【问题标题】:await does not resume context after async operation?异步操作后等待不恢复上下文?
【发布时间】:2015-11-20 14:56:20
【问题描述】:

我从 Noseratio 读到了 this question,它显示了在 awaitable 完成操作后 TaskScheduler.Current 不一样的行为。

答案表明:

如果没有正在执行的实际任务,那么TaskScheduler.CurrentTaskScheduler.Default相同

这是真的。我已经看过了here

  • TaskScheduler.Default
    • 返回ThreadPoolTaskScheduler 的一个实例
  • TaskScheduler.Current
    • 如果从正在执行的任务中调用,将返回TaskScheduler 当前正在执行的任务
    • 如果从任何其他地方调用将返回TaskScheduler.Default

但后来我想,如果是这样,让我们​​创建一个实际的Task(而不仅仅是Task.Yield())并对其进行测试:

async void button1_Click_1(object sender, EventArgs e)
{
    var ts = TaskScheduler.FromCurrentSynchronizationContext();
    await Task.Factory.StartNew(async () =>
    {
        MessageBox.Show((TaskScheduler.Current == ts).ToString()); //True

           await new WebClient().DownloadStringTaskAsync("http://www.google.com");

        MessageBox.Show((TaskScheduler.Current == ts).ToString());//False

    }, CancellationToken.None, TaskCreationOptions.None,ts).Unwrap();
}

第一个消息框是“真”,第二个是“假”

问题:

如你所见,我确实创建了一个实际任务。

我可以理解为什么第一个 MessageBox 产生 True。那是因为:

如果从正在执行的任务中调用,将返回 TaskScheduler 当前正在执行的任务

该任务确实有ts,即发送的TaskScheduler.FromCurrentSynchronizationContext()

但是 为什么 上下文是 not 保留在 second MessageBox ?对我来说,斯蒂芬的回答并不清楚。

附加信息:

如果我写的是(第二个消息框):

MessageBox.Show((TaskScheduler.Current == TaskScheduler.Default).ToString());

它确实产生了 true 。但为什么 ?

【问题讨论】:

  • 很好的问题,但如果task scheduler != synchronization context
  • 硬性规则的一个很好的例子:永远不要假设 TaskScheduler.Current 是你认为的那样 :) 对于任务操作 lambda 的范围,它只能保证是你传递给 Factory.StartNew 的内容(在您的情况下是Func<Task>,并在遇到第一个await时返回)。任何其他行为都应视为实现细节。

标签: c# .net async-await task-parallel-library


【解决方案1】:

混淆的原因如下:

  1. UI 没有“特殊”TaskScheduler。在 UI 线程上运行的代码的默认情况是 TaskScheduler.Current 存储 ThreadPoolTaskSchedulerSynchronizationContext.Current 存储 WindowsFormsSynchronizationContext(或其他 UI 应用中的相关)
  2. TaskScheduler.Current 中的 ThreadPoolTaskScheduler 不一定意味着它是用于运行当前代码的 TaskScheduler。这也意味着TaskSchdeuler.Current == TaskScheduler.Default“没有使用TaskScheduler
  3. TaskScheduler.FromCurrentSynchronizationContext() 不返回“实际”TaskScheduler。它返回一个“代理”,将任务直接发布到捕获的SynchronizationContext

因此,如果您在开始任务之前(或在任何其他地方)运行测试,您将得到与等待之后相同的结果

MessageBox.Show(TaskScheduler.Current == TaskScheduler.FromCurrentSynchronizationContext()); // False

因为TaskScheduler.CurrentThreadPoolTaskSchedulerTaskScheduler.FromCurrentSynchronizationContext() 返回一个SynchronizationContextTaskScheduler

这是您示例的流程:

  • 您从 UI 的SynchronizationContext(即WindowsFormsSynchronizationContext)创建一个新的SynchronizationContextTaskScheduler
  • TaskScheduler 上使用Task.Factory.StartNew 安排您创建的任务。由于它只是一个“代理”,它将委托发布到WindowsFormsSynchronizationContext,后者在 UI 线程上调用它。
  • 该方法的同步部分(第一次等待之前的部分)在 UI 线程上执行,同时与 SynchronizationContextTaskScheduler 关联。
  • 该方法到达 await 并在捕获 WindowsFormsSynchronizationContext 时“暂停”。
  • 当在等待后继续继续时,它会发布到 WindowsFormsSynchronizationContext 而不是 SynchronizationContextTaskScheduler,因为 SynchronizationContexts 具有优先权 (this can be seen in Task.SetContinuationForAwait)。然后它定期在 UI 线程上运行,没有任何“特殊”TaskScheduler 所以TaskScheduler.Current == TaskScheduler.Default

因此,创建的任务在使用 SynchronizationContext 的代理 TaskScheduler 上运行,但等待后的继续发布到 SynchronizationContext 而不是 TaskScheduler

【讨论】:

  • await 之后的延续在ThreadPoolTaskScheduler 上运行,而不是在 UI 上。
  • @YuvalItzchakov 你为什么这么说?我不同意。它使用 SynchronizationContext 发布到 UI
  • @YuvalItzchakov 该调度程序从不涉及。执行此操作的调度程序是SynchronizationContextTaskScheduler,它是在await 之前捕获的调度程序。
  • @YuvalItzchakov 它确实存在,只是不是TaskScheduler。它是一个 SynchronizationContext,它执行在 UI 线程上发布给它的操作(TaskScheduler.Current 返回 ThreadPoolTaskScheduler)。
  • 这似乎是正确的。 WinForms 从不设置TaskScheduler.Current。 TPL 在处理使用StartNew 创建的任务时会执行此操作。因此,如果不涉及 TPL,并且它不再处于第一次等待状态,TaskScheduler.Current 将恢复为默认值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-15
  • 2019-09-24
  • 1970-01-01
  • 2016-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多