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

await does not resume context after async operation?

我已经从 Noseratio 那里读到 this question,它显示了一个行为,其中 TaskScheduler.Current 在可等待对象完成其操作后与 不同

答案指出:

If there is no actual task being executed, then TaskScheduler.Current is the same as TaskScheduler.Default

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

  • TaskScheduler.Default
    • Returns an instance of the ThreadPoolTaskScheduler
  • TaskScheduler.Current
    • If called from within an executing task will return the TaskScheduler of the currently executing task
    • If called from any other place will return 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();
}

第一个消息框是 "True",第二个是 "False"

问题:

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

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

If called from within an executing task will return the TaskScheduler of the currently executing task

而且那个任务确实有 ts 是发送的 TaskScheduler.FromCurrentSynchronizationContext()

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

附加信息:

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

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

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

混淆的原因如下:

  1. UI 没有 "special" TaskScheduler。 UI 线程上代码 运行ning 的默认情况是 TaskScheduler.Current 存储 ThreadPoolTaskSchedulerSynchronizationContext.Current 存储 WindowsFormsSynchronizationContext (或相关的在其他 UI 应用中)
  2. ThreadPoolTaskScheduler in TaskScheduler.Current 并不一定意味着它是 TaskScheduler 用于 运行 当前代码段。它还表示 TaskSchdeuler.Current == TaskScheduler.Default"there is no TaskScheduler being used"
  3. TaskScheduler.FromCurrentSynchronizationContext() 不是 return "acutal" TaskScheduler。它 return 是一个 "proxy" 将任务直接发布到捕获的 SynchronizationContext

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

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

因为 TaskScheduler.CurrentThreadPoolTaskSchedulerTaskScheduler.FromCurrentSynchronizationContext() return 是 SynchronizationContextTaskScheduler.

这是您示例的流程:

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

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