await不会在异步操作后恢复上下文

本文关键字:恢复 上下文 异步操作 await | 更新日期: 2023-09-27 18:09:51

我从Noeratio中读到了这个问题,它显示了一种行为,在awaitable完成操作后,TaskScheduler.Current不一样

答案是:

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

这是真的。我已经在这里看到了:

  • 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",第二个为"False">

问题:

正如你所看到的,我确实创建了一个实际的任务。

我能理解为什么第一个MessageBox会产生True。这是因为:

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

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

但是为什么上下文没有保存在第二个MessageBox?对我来说,斯蒂芬的回答并不清楚。

附加信息:

如果我写(而不是第二个消息框(:

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

它确实产生CCD_ 14。但为什么呢?

await不会在异步操作后恢复上下文

混淆的原因如下:

  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

因为CCD_ 28是CCD_ 29,而CCD_。

这是您的示例的流程:

  • 您可以从UI的SynchronizationContext(即WindowsFormsSynchronizationContext(创建一个新的SynchronizationContextTaskScheduler
  • 在该TaskScheduler上安排使用Task.Factory.StartNew创建的任务。由于它只是一个"代理",它将委托发布到WindowsFormsSynchronizationContext,CCD_37在UI线程上调用它
  • 该方法的同步部分(第一次等待之前的部分(在UI线程上执行,同时与SynchronizationContextTaskScheduler相关联
  • 该方法到达等待并在捕获该WindowsFormsSynchronizationContext时被"挂起">
  • 当在等待之后继续时,它被发布到WindowsFormsSynchronizationContext而不是SynchronizationContextTaskScheduler,因为SynchronizationContext具有优先级(这可以在Task.SetContinuationForAwait中看到(。然后它在UI线程上定期运行,没有任何"特殊"的TaskScheduler,因此TaskScheduler.Current == TaskScheduler.Default

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