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

时间:2016-05-30 11:24:58

标签: c# async-await

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

作为参考,这里有一个非常基本的演示如何设置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;
    }
}

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

现在,我希望能够从外部上下文(例如,从UI线程)向此发布Task,但似乎无法找到干净的方法。

一种方法是使用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;
});

但这是重新发明轮子和支持变体的主要痛苦,例如StartNew(Func<TResult>)StartNew(Func<Task<TResult>>)等。

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

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

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

2 个答案:

答案 0 :(得分:5)

似乎默认SynchronizationContextTaskScheduler

  1. 内部
  2. 仅适用于当前的同步上下文
  3. 但它的源代码可用here,我们看到它相对简单,所以我们可以尝试推出自己的调度程序,如下所示:

    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);
        }
    }
    

    然后您的CreateTaskFactory变为:

    TaskFactory CreateTaskFactory() {
        return new TaskFactory(new MySynchronizationContextTaskScheduler(SomeDispatcher));
    }
    

    您可以使用以下方式创建任务:

    var factory = CreateTaskFactory();
    var task = factory.StartNew(...);
    

答案 1 :(得分:3)

Parallel Extensions Extras contains SynchronizationContextTaskScheduler完全符合您的要求。

如果您不想自己编译PEE,there is an unofficial NuGet package for it

请注意,您通常不需要这样做,而且您要求这样做的事实可能表明您的设计存在缺陷。