首页 > 代码库 > 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