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

Roy*_*mir 20 .net c# task-parallel-library async-await

我已经 从Noseratio那里读到了这个问题,这个问题显示了一个在等待完成其操作之后TaskScheduler.Current一样的行为.

答案指出:

如果没有执行任何实际任务,则与之TaskScheduler.Current 相同TaskScheduler.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();
}
Run Code Online (Sandbox Code Playgroud)

第一个Messagebox是"True",第二个是"False"

题:

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

我可以理解为什么第一个MessageBox产量True.多数民众赞成:

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

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

为什么第二个 MessageBox中没有保留 上下文?对我来说,斯蒂芬的回答并不清楚.

附加信息 :

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

MessageBox.Show((TaskScheduler.Current == TaskScheduler.Default).ToString());
Run Code Online (Sandbox Code Playgroud)

它确实屈服了true.但为什么 ?

i3a*_*non 10

混淆的原因是:

  1. 用户界面没有"特殊" TaskScheduler.在UI线程上运行的代码的默认情况是TaskScheduler.Current存储ThreadPoolTaskSchedulerSynchronizationContext.Current存储WindowsFormsSynchronizationContext(或其他UI应用程序中的相关代码)
  2. ThreadPoolTaskSchedulerin TaskScheduler.Current并不一定意味着它TaskScheduler被用来运行当前的代码.这也意味着TaskSchdeuler.Current == TaskScheduler.Default,因此"没有TaskScheduler用的存在".
  3. TaskScheduler.FromCurrentSynchronizationContext()没有返回"acutal" TaskScheduler.它返回一个"代理",将任务直接发布到捕获的SynchronizationContext.

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

MessageBox.Show(TaskScheduler.Current == TaskScheduler.FromCurrentSynchronizationContext()); // False
Run Code Online (Sandbox Code Playgroud)

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

这是你的例子的流程:

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

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

  • 这似乎是正确的.WinForms永远不会设置`TaskScheduler.Current`.TPL在处理用`StartNew`创建的任务时执行此操作.因此,如果不涉及TPL,并且它不再是第一次等待,那么`TaskScheduler.Current`将恢复为默认值. (2认同)