如何创建发布到给定SynchronizationContext的任务?

ant*_*tak 3 c# async-await

给定a SynchronizationContext,我已经拥有(并且基本上是特定线程的窗口),如何创建Task发布到此上下文的s?

作为参考,这里是一个非常基本的演示如何SynchronizationContext设置.

public class SomeDispatcher : SynchronizationContext
{
    SomeDispatcher() {

        new Thread(() => {

            SynchronizationContext.SetSynchronizationContext(this);

            // Dispatching loop (among other things)

        }).Start();
    }

    override void Post(SendOrPostCallback d, object state)
    {
        // Add (d, state) to a dispatch queue;
    }
}
Run Code Online (Sandbox Code Playgroud)

这适用于已在上下文中运行的异步/等待.

现在,我希望能够Task从外部上下文(例如,从UI线程)发布s,但似乎无法找到一种干净的方式来做到这一点.

一种方法是使用TaskCompletionSource<>.

Task StartTask(Action action)
{
    var tcs = new TaskCompletionSource<object>();
    SaidDispatcher.Post(state => {
        try
        {
            action.Invoke();
            tcs.SetResult(null);
        }
        catch (Exception ex)
        {
            tcs.SetException(ex);
        }
    });
    return tcs.Task;
});
Run Code Online (Sandbox Code Playgroud)

但是,这是重新发明轮子和一个重大的痛苦支撑变型例如StartNew(Func<TResult>),StartNew(Func<Task<TResult>>)

它的TaskFactory接口SynchronizationContext可能是理想的,但我似乎无法干净地实例化:

TaskFactory CreateTaskFactory()
{
    var original = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(SomeDispatcher); // yuck!
    try
    {
        return new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
    }
    finally
    {
        SynchronizationContext.SetSynchronizationContext(original);
    }
}
Run Code Online (Sandbox Code Playgroud)

(即必须暂时软管当前线程的同步上下文似乎很麻烦.)

Evk*_*Evk 5

看来默认SynchronizationContextTaskScheduler

  1. 内部
  2. 仅适用于当前同步上下文

但它的源代码在这里可用,我们看到它相对简单,所以我们可以尝试推出自己的调度程序,如下所示:

public sealed class MySynchronizationContextTaskScheduler : TaskScheduler {
    private readonly SynchronizationContext _synchronizationContext;

    public MySynchronizationContextTaskScheduler(SynchronizationContext context) {
        _synchronizationContext = context;
    }

    [SecurityCritical]
    protected override void QueueTask(Task task) {
        _synchronizationContext.Post(PostCallback, task);
    }

    [SecurityCritical]
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) {
        if (SynchronizationContext.Current == _synchronizationContext) {
            return TryExecuteTask(task);
        }
        else
            return false;
    }

    [SecurityCritical]
    protected override IEnumerable<Task> GetScheduledTasks() {
        return null;
    }

    public override Int32 MaximumConcurrencyLevel
    {
        get { return 1; }
    }

    private void PostCallback(object obj) {
        Task task = (Task) obj;
        base.TryExecuteTask(task);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你CreateTaskFactory变成:

TaskFactory CreateTaskFactory() {
    return new TaskFactory(new MySynchronizationContextTaskScheduler(SomeDispatcher));
}
Run Code Online (Sandbox Code Playgroud)

你用以下方法创建任务:

var factory = CreateTaskFactory();
var task = factory.StartNew(...);
Run Code Online (Sandbox Code Playgroud)