自定义 TaskFactory 不使用自定义 SynchronizationContext

Custom TaskFactory does not use custom SynchronizationContext

注:这是在Unity3D下

我需要 运行 使用自定义同步上下文的内部任务,如下所示:

这是因为内部任务中的某些对象只能在自定义同步上下文将 post 到的特定线程上创建,另一方面,我希望外部任务是 运行 通常,即 ThreadPoolTask.Run 使用的任何内容。

遵循这个问题的建议:

How to run a Task on a custom TaskScheduler using await?

不幸的是,这不起作用,同步上下文仍然是默认的:

OUTER JOB: 'null'
0
        INNER JOB: 'null' <------------- this context should not be the default one
        0,      1,      2,      3,      4,
1
        INNER JOB: 'null'
        0,      1,      2,      3,      4,
2
        INNER JOB: 'null'
        0,      1,      2,      3,      4,
Done, press any key to exit

代码:

using System;
using System.Threading;
using System.Threading.Tasks;

internal static class Program
{
    private static TaskFactory CustomFactory { get; set; }

    private static async Task Main(string[] args)
    {
        // create a custom task factory with a custom synchronization context,
        // and make sure to restore initial context after it's been created

        var initial = SynchronizationContext.Current;

        SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext());

        CustomFactory = new TaskFactory(
            CancellationToken.None,
            TaskCreationOptions.DenyChildAttach,
            TaskContinuationOptions.None,
            TaskScheduler.FromCurrentSynchronizationContext()
        );

        SynchronizationContext.SetSynchronizationContext(initial);


        // now do some work on initial context

        await Task.Run(async () =>
        {
            Console.WriteLine($"OUTER JOB: {GetCurrentContext()}");

            for (var i = 0; i < 3; i++)
            {
                Console.WriteLine(i);
                await Task.Delay(100);

                // now do some work on custom context
                await RunOnCustomScheduler(DoSpecialWork);
            }
        });

        Console.WriteLine("Done, press any key to exit");
        Console.ReadKey();
    }

    private static void DoSpecialWork()
    {
        Console.WriteLine($"\tINNER JOB: {GetCurrentContext()}"); // TODO wrong context

        for (var i = 0; i < 5; i++)
        {
            Thread.Sleep(250);
            Console.Write($"\t{i}, ");
        }

        Console.WriteLine();
    }

    private static Task RunOnCustomScheduler(Action action)
    {
        return CustomFactory.StartNew(action);
    }

    private static string GetCurrentContext()
    {
        return SynchronizationContext.Current?.ToString() ?? "'null'";
    }
}

public class CustomSynchronizationContext : SynchronizationContext
{
}

当我调试时,我可以看到自定义工厂确实有带有自定义上下文的自定义调度程序,但实际上它不起作用。

问题:

如何让我的自定义调度程序使用创建它的自定义上下文?

最终答案:

我正在等待我想避免使用的线程内部方法,将这些东西包装在 await Task.Run 中(加上一些为简洁起见而省略的细节)修复了它。现在我可以 运行 我在 Unity 线程之外的长期任务,但仍然使用上面提到的自定义工厂方法在其中进行 Unity 调用。

即它已经在工作了,但我没有正确使用它

您可以使用 ReactiveExtensions (nuget System.Reactive) 轻松实现:

public static Task RunOnCustomScheduler(Action action, IScheduler scheduler)
{
    return Observable.Return(Unit.Default)
        .ObserveOn(scheduler) // switch context
        .Do(_ => action.Invoke()) // do the work
        // .Select(_ => func.Invoke()) // change Action to Func if you want to return stuff.
        //.Select(async _ => await func.Invoke()) // can work with async stuff, though ConfigureAwait may become tricky
        .ToTask();
}

您也可以传递 SynchronizationContext 而不是 IScheduler。

记得 ConfigureAwait(false/true) 虽然当您调用此方法以配置您希望如何继续时

您的 CustomSynchronizationContext 确实用于 运行 宁 DoSpecialWork 方法。您可以通过覆盖基础 class:

Post 方法来确认这一点
public class CustomSynchronizationContext : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        Console.WriteLine($"@Post");
        base.Post(d, state);
    }
}

然后再次 运行 你的程序,你会看到三个 @Post 出现在控制台中。

SynchronizationContext.Current is null is because this property is associated with the current thread. Here is the source code的原因:

// Get the current SynchronizationContext on the current thread
public static SynchronizationContext Current 
{
    get      
    {
        return Thread.CurrentThread.GetExecutionContextReader().SynchronizationContext ?? GetThreadLocalContext();
    }
}

您可以在 ThreadPool 的每个线程中安装 CustomSynchronizationContext,但不推荐这样做。这也不是必需的。在没有 SynchronizationContext.Current 的情况下,异步延续在当前 TaskScheduler 中是 运行ning,而 DoSpecialWorkAsync 方法中的当前 TaskScheduler 是您已使用与您的 CustomSynchronizationContext 实例关联的 TaskScheduler.FromCurrentSynchronizationContext() 方法创建了自己。