【问题标题】:How do I create Tasks that are posted to a given SynchronizationContext?如何创建发布到给定 SynchronizationContext 的任务?
【发布时间】:2016-05-30 21:05:00
【问题描述】:

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

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

这适用于已经在上下文中运行的 async / await

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

一种方法是使用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&lt;TResult&gt;)StartNew(Func&lt;Task&lt;TResult&gt;&gt;) 等变体的主要痛苦。

SynchronizationContextTaskFactory 接口可能是理想的,但我似乎无法干净地实例化一个:

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

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

【问题讨论】:

  • 如何通过复制现有 SynchronizationContextTaskScheduler 的逻辑来实现自定义 TaskScheduler?当然不需要复制,逻辑本身看起来很简单。
  • @Evk 有几个internals 你不能使用,但主要思想仍然存在——你需要自己的TaskScheduler
  • 为什么要将任务发布到同步上下文?同步上下文的通常用途是从同步上下文开始。
  • @antak:如果你只想要一个单线程的syncctx/taskfactory,那么你可能会发现我的AsyncContextThread 类型很有帮助。

标签: c# async-await


【解决方案1】:

看来默认SynchronizationContextTaskScheduler

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

但是它的源代码是可用的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(...);

【讨论】:

  • 考虑到问题是如何添加任务,而不是如何编写任务调度程序,您能否还添加一个如何使用它的示例?
  • 这看起来很棒!我想知道他们为什么不提供TaskScheduler.FromSynchronizationContext(SynchronizationContext),如果是因为允许任何旧的同步上下文存在一些设计问题,但我想不出任何问题。 (编辑:删除噪音)
  • @antak,它还说“返回的枚举不应该为空。如果当前没有排队的任务,则应该返回一个空的枚举。”所以似乎实际的实现确实违反了这一点。至于他们为什么不提供 - 可能他们没有发现任何用处。请注意,他们的 SynchronizationContextTaskScheduler 实现也没有办法为非当前上下文进行初始化,所以他们自己没有使用它,即使在内部也没有
  • @Evk 是的,我从我的 cmets 中删除了关于 GetScheduledTasks() 的那部分,因为文档没有具体说明未实现的案例,并且因为参考源有调用者正在检查 @987654329 @s.
【解决方案2】:

Parallel Extensions Extras contains SynchronizationContextTaskScheduler 正是你想要的。

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

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

【讨论】:

  • 很高兴知道至少间接支持此用例。 您通常不需要这样做:假设我有一个高度定制的任务调度程序(一个在其核心具有任务调用循环的单线程),我想(可能追溯地)添加 async / await 支持。自然进程似乎是,实现SynchronizationContext -> TaskScheduler -> TaskFactory。有没有更好的办法? (或者您是说需要创建一个可能是问题的自定义调度程序?)
  • @antak 为什么你还需要那个SynchronizationContext?自定义TaskScheduler 不够吗?
  • 我兴奋了一会儿,因为我想我可以将TaskScheduler.Current 而不是syncctx.Current 设置为get awaits to resume in the same thread。但是,TaskScheduler.Currentappears to be something quite different 并不是我可以设置的线程本地。如果调度程序所做的一切都是通过TaskScheduler 进行的,那么这可能不是问题,但是当它调用Task 之外的例程时,await 就会开始中断。有什么我想念的吗?
猜你喜欢
  • 2019-10-25
  • 1970-01-01
  • 1970-01-01
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-15
  • 1970-01-01
相关资源
最近更新 更多