我阅读this article有关Task.ConfigureAwait
的内容,这有助于防止异步代码死锁。
看看这段代码:(我知道我不应该.Result
,但它是问题的一部分)
private void Button_Click(object sender, RoutedEventArgs e)
{
string result = GetPageStatus().Result;
Textbox.Text = result;
}
public async Task<string> GetPageStatus()
{
using (var httpClient = new HttpClient())
{
var response = await httpClient.GetAsync("http://www.google.com");
return response.StatusCode.ToString();
}
}
这将导致死锁,因为:
.Result
- 操作将在等待异步操作完成时阻止当前线程(即UI线程)。
网络调用完成后,将尝试继续对捕获的上下文执行response.StatusCode.ToString()
- 方法。 (被封锁 - 因此死锁)。
一种解决方案是使用:
var response = await httpClient.GetAsync("http://www.google.com").ConfigureAwait(false);
但是其他解决方案是一直异步(没有阻塞):
/*1*/ private async void Button_Click(object sender, RoutedEventArgs e)
/*2*/ {
/*3*/ string result = await GetPageStatus();
/*4*/ Textbox.Text = result;
/*5*/ }
/*6*/ public async Task<string> GetPageStatus()
/*7*/ {
/*8*/ using (var httpClient = new HttpClient())
/*9*/ {
/*10*/ var response = await httpClient.GetAsync("http://www.google.com");
/*11*/ return response.StatusCode.ToString();
/*12*/ }
/*13*/ }
问题:
(我试图了解此代码如何帮助解决问题 - 通过上下文POV)。
行#3
和行#10
是否会捕获不同的上下文?
我认为对于流动的方式我是对的:
第3行调用#6(调用#10)并看到它还没有完成,所以等待(#3 = UI线程的捕获上下文)。
稍后,第10行会在完成后捕获另一个上下文(我将其称为newContext ),它将返回&#34; newContext&#34;然后释放UI上下文(线程)。
我是对的吗?
答案 0 :(得分:4)
第3行和第10行是否捕获不同的上下文?
正如你的代码看起来那样,没有。它们都将捕获相同的UI同步上下文,因为您不使用ConfigureAwait(false)
来阻止将连续编组回UI环境。
我认为对流动的方式是正确的:
第3行调用#6(调用#10)并看到它还没有完成, 所以它等待(捕获#3 = UI线程的上下文)。
稍后,第10行捕获另一个上下文(我将称之为 newContext)完成之后,它回到&#34; newContext&#34;然后 发布UI上下文(线程)。
几乎。没有&#34;新的背景&#34;正在通话中创建。它始终是相同的UI同步上下文。例如,如果你有两个异步调用,一个使用ConfigureAwait(false)
,第二个调用将继续在线程池线程上执行。
对于可视化,它确实可以正确捕获代码的执行流程。
答案 1 :(得分:3)
没有不同的背景。在这两种情况下,SyncrhonizationContext
都是单线程UI同步上下文(只要您不使用ConfigureAwait(false)
)
当UI线程同步等待自身时发生死锁。您可以通过避免使用ConfigureAwait(false)
的UI线程或通过避免Task.Result
来阻止它来解决这个问题。
“一路async
解决死锁的原因是UI线程不再被阻止,可以自由运行两个async
操作的延续。
所以:
SyncrhonizationContext
。#11
任务完成GetAsync
任务后,行GetPageStatus
将在UI线程(未被阻止)上恢复。然后行#4
也将在UI线程上恢复。重要的是要理解在这种情况下SynchronizationContext
只能确保专用线程(串行)完成一些工作。只要线程可以自由执行该工作单元,就永远不会发生死锁。
答案 2 :(得分:0)
我假设这是WinForms/WPF/Asp.NET
应用程序,因此在第3行中将捕获UI
线程同步上下文,并且任务将在线程池线程中运行。因此,将从线程池线程调用第10行,并使用默认调度程序。