首页 > 代码库 > Async/Await Context 与 ConfigureAwait
Async/Await Context 与 ConfigureAwait
Context
当你await一个awaitable对象的时候,编译器会捕捉当前的context,并且在执行await之后的代码时应用这个context。
那么,context具体是什么呢?
简单地说:
1. 如果你在一个UI线程上,那么context就是一个UI context
2. 如果你在相应ASP.NET Request的线程上,那么它就是一个ASP.NET request context
3. 否则,它就是一个thread pool context
更加准确点说
1. 如果SynchronizationContext.Current不是null,那么context就是SynchronizationContext.Current
2. 否则,context是当前的TaskScheduler
为什么要这样设计呢?一个很重要的原因是为了让UI/ASP.NET的异步编程变得更加容易,透明。
private async void DownloadFileButton_Click(object sender, EventArgs e){ // Since we asynchronously wait, the UI thread is not blocked by the file download. await DownloadFileAsync(fileNameTextBox.Text); // Since we resume on the UI context, we can directly access UI elements. resultTextBox.Text = "File downloaded!";}
Avoid Context
很多时候,我们希望编译器不要把context保存下来,并且在await之后恢复。这样做有两个原因,1. 提高效率 2. 避免死锁
提高效率
通常一个await之后,可能会有另外一个或者多个await,或者await之后,没有修改UI等操作,因此也就不需要必须回到MAIN THREADS上去。
private async Task DownloadFileAsync(string fileName){ // Use HttpClient or whatever to download the file contents. var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false); // Note that because of the ConfigureAwait(false), we are not on the original context here. // Instead, we‘re running on the thread pool. // Write the file contents out to a disk file. await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false); // The second call to ConfigureAwait(false) is not *required*, but it is Good Practice.}
避免死锁
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(); }}
这段代码看起来很正常,但是会死锁。
为什么呢
1. GetPageStatus.Result会开始async的Task,然后await HttpClient返回。这时候,执行context,即UI context会被保存下来。等到HttpClient返回之后,执行response.StatusCode.ToString()时恢复这个context。
2. 同时,.Result 操作会阻塞当前线程,即UI线程,等到GetPageStatus执行完成并且返回,读取结果。
3. 个么问题来了,当HttpClient返回之后,编译器期望把第一步保存的context,即UI context拿过来,执行response.StatusCode.ToString(),但是UI线程被block住了,必须等待GetPageStatus执行结束之后才可用。这就形成了死锁。
要解决这个问题也很简单。
我们可以不用.Result,而是用await。这才是提倡的做法
private async void Button_Click(object sender, RoutedEventArgs e){ string result = await GetPageStatus(); 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呢,那也是有办法的。就是用上面提到的ConfigureAwait,不让编译器保存context
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").ConfigureAwait(false); return response.StatusCode.ToString(); }}
Async/Await Context 与 ConfigureAwait