在阅读Stephen Toub's article on 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 ThreadPoolTaskScheduler
ProcessMessage ThreadPoolTaskScheduler
PerformServiceCall 3 ThreadPoolTaskScheduler
但是我希望PerformServiceCall 1不会出现在WindowsFormsSynchronizationContext上,因为文章指出" SynchronizationContext.Current不会在等待点“流动”#34; ...
使用Task.Run和async lambda调用PerformServiceCall时,上下文不会被传递,如下所示:
await Task.Run(async () =>
{
await PerformServiceCall();
}).ConfigureAwait(false);
任何人都可以澄清或指出一些文件吗?
答案 0 :(得分:7)
Stephen的文章解释说SynchronizationContext
不像ExecutionContext
那样“流动”(尽管SynchronizationContext
是ExecutionContext
的一部分)。
ExecutionContext
总是流淌。即使您使用Task.Run
,如果SynchronizationContext
与Task.Run
一起流动Task.Run
也会在UI线程上执行,因此SynchronizationContext
将毫无意义。
await
不会流动,而是在到达异步点(即ExecutionContext
)并且在其后发布后继续捕获(除非另有明确说明)。
这句话解释了差异:
现在,我们有一个非常重要的观察结果:流动
SynchronizationContext
在语义上与捕获和发布到ExecutionContext
非常不同。当您流
SynchronizationContext
时,您正在从一个线程捕获状态,然后在提供的委托执行期间恢复该状态,使其处于环境状态。这不是捕获和使用SynchronizationContext.Post
时发生的情况。捕获部分是相同的,因为你从当前线程中获取数据,但是然后你以不同的方式使用该状态。而不是在调用委托期间使该状态变为当前状态,而是Post
,您只是使用捕获的状态来调用委托。该委托的运行地点,时间和方式完全取决于PerformServiceCall 1
方法的实现。
这意味着在您的情况下,当您输出SynchronizationContext
当前WindowsFormsSynchronizationContext
确实是await
因为您尚未到达任何异步点并且您仍然在UI线程中(请记住,async
方法中第一个LogSyncContext("PerformServiceCall 1");
之前的部分是在调用线程上同步执行的,所以ConfigureAwait(false)
发生在从{PerformServiceCall
返回的任务发生之前SynchronizationContext
1}})。
当您使用ConfigureAwait(false)
(忽略捕获的SynchronizationContext
)时,您只能“取消”用户界面HttpClient.GetAsync
。第一次发生在PerformServiceCall
,然后再次发送到{{1}}。