Sti*_*tif 5 .net c# synchronizationcontext async-await
在阅读了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));
}
Run Code Online (Sandbox Code Playgroud)
输出是:
btnDoSomething_Click WindowsFormsSynchronizationContext
DoItAsync WindowsFormsSynchronizationContext
PerformServiceCall 1 WindowsFormsSynchronizationContext
PerformServiceCall 2 ThreadPoolTaskScheduler
ProcessMessage ThreadPoolTaskScheduler
PerformServiceCall 3 ThreadPoolTaskScheduler
但我希望PerformServiceCall 1不在WindowsFormsSynchronizationContext上,因为文章声明"SynchronizationContext.Current不会"跨越等待点"..."
使用Task.Run和async lambda调用PerformServiceCall时,上下文不会被传递,如下所示:
await Task.Run(async () =>
{
await PerformServiceCall();
}).ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)
任何人都可以澄清或指出一些关于此的文件吗?
斯蒂芬的文章解释说,SynchronizationContext不会"流动" ExecutionContext(尽管SynchronizationContext是其中的一部分ExecutionContext).
ExecutionContext总是流淌着.即使你使用它Task.Run,如果SynchronizationContext流动它Task.Run会在UI线程上执行,所以Task.Run没有意义.
SynchronizationContext它不会流动,而是在到达异步点(即await)时被捕获,并且在它被发布到它之后继续被捕获(除非另有明确说明).
这个引用解释了差异:
现在,我们有一个非常重要的观察要做:流动
ExecutionContext在语义上与捕获和发布到一个非常不同SynchronizationContext.流动时
ExecutionContext,您将从一个线程捕获状态,然后在提供的委托执行期间恢复该状态,使其处于环境状态.这不是捕获和使用时发生的情况SynchronizationContext.捕获部分是相同的,因为你从当前线程中获取数据,但是然后你以不同的方式使用该状态.而不是在调用委托期间使该SynchronizationContext.Post状态变为当前状态,而只是使用捕获的状态来调用委托.该委托的运行位置,时间和方式完全取决于该Post方法的实现.
这意味着在您的情况下,当您输出PerformServiceCall 1当前SynchronizationContext确实是WindowsFormsSynchronizationContext因为您还没有到达任何异步点并且您仍然在UI线程中(请记住,方法中第await一个之前的部分async是在调用时同步执行的线程LogSyncContext("PerformServiceCall 1");发生在ConfigureAwait(false)从返回的任务发生之前PerformServiceCall).
您只能SynchronizationContext在使用时"离开"UI ConfigureAwait(false)(忽略捕获的SynchronizationContext).第一次发生HttpClient.GetAsync,然后再次发生PerformServiceCall.