6

在阅读了 Stephen Toub 关于 SynchronizationContext 的文章后,我对这段 .NET 4.5 代码的输出提出了一个问题:

private void btnDoSomething_Click()
{
    LogSyncContext("btnDoSomething_Click");
    DoItAsync().Wait();
}
private async Task DoItAsync()
{
    LogSyncContext("DoItAsync");
    await PerformServiceCall().ConfigureAwait(false); //to avoid deadlocking
}
private async Task PerformServiceCall()
{
    LogSyncContext("PerformServiceCall 1");
    HttpResponseMessage message = await new HttpClient
    {
        BaseAddress = new Uri("http://my-service")
    }
    .GetAsync("/").ConfigureAwait(false); //to avoid deadlocking
    LogSyncContext("PerformServiceCall 2");
    await ProcessMessage(message);
    LogSyncContext("PerformServiceCall 3");
}

private async Task ProcessMessage(HttpResponseMessage message)
{
    LogSyncContext("ProcessMessage");
    string data = await message.Content.ReadAsStringAsync();
    //do something with data
}

private static void LogSyncContext(string statementId)
{
    Trace.WriteLine(String.Format("{0} {1}", statementId, SynchronizationContext.Current != null ? SynchronizationContext.Current.GetType().Name : TaskScheduler.Current.GetType().Name));
}

输出是:

btnDoSomething_Click WindowsFormsSynchronizationContext

DoItAsync WindowsFormsSynchronizationContext

PerformServiceCall 1 WindowsFormsSynchronizationContext

PerformServiceCall 2 线程池任务调度器

ProcessMessage ThreadPoolTask​​Scheduler

PerformServiceCall 3 线程池任务调度器

但我希望 PerformServiceCall 1 不会出现在 WindowsFormsSynchronizationContext 上,因为文章指出“SynchronizationContext.Current 不会在等待点之间“流动”...

当使用 Task.Run 和异步 lambda 调用 PerformServiceCall 时,上下文不会被传递,如下所示:

await Task.Run(async () =>
{
    await PerformServiceCall();
}).ConfigureAwait(false);

任何人都可以澄清或指出一些关于此的文件吗?

4

1 回答 1

8

Stephen 的文章解释说,SynchronizationContext它不会像现在那样“流动” ExecutionContext(尽管SynchronizationContext它是 的一部分ExecutionContext)。

ExecutionContext总是流动的。即使您使用Task.Runso ifSynchronizationContextTask.Run在 UI 线程上执行,因此Task.Run将毫无意义。 SynchronizationContext不流动,而是在await达到异步点(即)时捕获它,并将其发布到它之后的延续(除非另有明确说明)。

这段引用解释了差异:

现在,我们有一个非常重要的观察:流动ExecutionContext在语义上与捕获和发布到SynchronizationContext.

当您 flow 时ExecutionContext,您从一个线程捕获状态,然后恢复该状态,使其在提供的委托执行期间处于环境状态。当您捕获和使用SynchronizationContext. 捕获部分是相同的,因为您正在从当前线程中获取数据,但是您随后会以不同的方式使用该状态。而不是在调用委托期间使该状态成为当前状态,SynchronizationContext.Post您只需使用捕获的状态来调用委托。该委托的运行地点、时间和方式完全取决于Post方法的实现。

这意味着在您的情况下,当您输出PerformServiceCall 1The currentSynchronizationContext确实是WindowsFormsSynchronizationContext因为您尚未达到任何异步点并且您仍在 UI 线程中(请记住,方法中第await一个之前的部分在async调用时同步执行线程因此LogSyncContext("PerformServiceCall 1");发生在ConfigureAwait(false)PerformServiceCall) 返回的任务上发生之前。

SynchronizationContext您仅在使用时“下车” UI ConfigureAwait(false)(忽略捕获的SynchronizationContext)。第一次发生是 on HttpClient.GetAsync,然后又是 on PerformServiceCall

于 2015-04-01T10:02:40.557 回答