Async and Await 非同步和等待

weixin_33831673發表於2015-09-25

【第一次這麼耐下性子認真寫部落格,雖然覺得很認真了,當畢竟是第一次嘛,以後再看肯定覺得很不咋滴的,更何況園子裡有那麼多的高人和大俠,這篇文章就權當練練手了,熟悉一下用客戶端發表部落格了,也希望大家多多照顧新人,這廂有禮了微笑!】下面正式開始,GO!


 

目錄

Introducing the Keywords 介紹關鍵字
Awaitables 非同步操作
Return Types 返回型別
Returning Values 返回值
Context  上下文
Avoiding Context:避免上下文
Async Composition 非同步組合
Guidelines 指南
Next Steps 下一步

首先,號外號外:非同步將從根本上改變大多數編寫程式碼的方式。是的,我相信非同步/等待將會比Linq的影響更大。理解非同步將會是未來幾年的基本必須品。

Introducing the Keywords 介紹關鍵字

最簡單的非同步方法的樣子就像下面這樣:

public async TaskDoSomethingAsync()
{
 awaitTask.Delay(100);//非同步等待100ms
}

async關鍵字在這個方法中啟用了await關鍵字,並且改變了方法處理結果的方式。這就是async關鍵字所做的!它沒有線上程池的執行緒上執行這個方法,也沒有做任何其他奇妙的事情。async關鍵字僅僅啟用了await關鍵字(並且管理方法結果)。

非同步方法從一開始就像其他任何方法一樣執行下去,即,一開始同步執行直到遇到一個”await“(或者丟擲一個異常)。

await關鍵字是可以獲得非同步的地方。await就像一個一元操作符:它只需要一個引數,一個awaitable(一個"awaitable"是一個非同步操作)。await檢查非同步操作是否已經完成;如果非同步操作已經完成了,那麼這個方法就繼續執行(就像一個普通的同步方法一樣)。

如果await看到了非同步操作沒有完成,那麼它就非同步執行。它告訴這個非同步操作當此非同步操作本身完成的時候,執行這個方法的剩餘部分,然後返回到該非同步方法。

然後,當非同步操作完成時,將執行非同步方法的剩餘部分。如果你正在等待一個內建的非同步操作(比如一個任務),然後非同步方法的剩餘部分將會在”await“返回之前被捕捉到的”上下文“上執行。

我喜歡把”await“看做是”非同步的await“。那就是說,在非同步操作完成之前,非同步方法一直是暫停的(因此它等待),但是實際的執行緒並沒有阻塞(所以它是非同步的)。

Awaitables 非同步操作

.NET framework中有2種已經共用的非同步操作型別: Task<T> 和Task。也有其他的非同步操作型別:特殊的方法,如“Task.Yield”返回不是任務型別的非同步操作。你也可以建立自己的非同步操作(經常出於效能原因考慮),或者使用擴充套件方法來產生非非同步操作型別的非同步操作。

關於非同步操作的很重要的一點是:這個型別是非同步操作,此型別不是指方法返回的型別。換言之,你可以等待返回Task的非同步方法的結果...因為這個方法返回Task,而不是因為它是非同步的。所以你可以等待返回Task的非非同步的方法的結果,如下:

public async Task NewStuffAsync()
{
  // 使用await.
  await ...
}

public Task MyOldTaskParallelLibraryCode()
{
  // 注意該方法不是一個非同步方法,不能在該方法體內使用await
  ...
}

public async Task ComposeAsync()
{
  // 我們可以 await Tasks,不管它們來自哪裡.
  await NewStuffAsync();
  await MyOldTaskParallelLibraryCode();
}

提示:如果你有一個簡單的非同步的方法,你可以不使用await關鍵字來實現它(如使用Task.FromResult)。如果你可以不使用await,那就不要使用,並且移除async關鍵字。一個返回Task.FromResult的非非同步方法比返回一個值的非同步方法更高效。

Return Types 返回型別

非同步方法可以返回Task,Task<T>或者void。在幾乎所有情況下,有可以返回Task或Task<T>,只有必要時才返回void。

為啥返回Task或者Task<T>呢?因為他們是非同步操作,而void不是。所以如果你有一個返回Task或者Task<T>的方法,那麼你可以把結果傳遞給await。對於返回值為void的方法,你沒有東西傳給await。當你有同步事件控制程式碼的時候,必須返回void。

對於其他一些高階的操作你可以使用async void,比如一個單獨的“static async void MainAsync()”控制檯程式。然而,async void的使用有它自己的問題,詳見非同步控制檯程式。async void方法最關鍵的用例是事件控制程式碼。

Returning Values 返回值

非同步方法返回型別為Task或者void,表示沒有返回值,返回一個Task<T>表示必須返回一個T型別的值。

public async Task<int> CalculateAnswer()
{
  await Task.Delay(100); // (Probably should be longer...)

  // Return  "int"型別, 不是"Task<int>"
  return 42;
}

雖然習慣起來有一點彆扭,但這有一些背後設計的原因(非同步CTP(Async CTP)為什麼那樣工作?

Context  上下文

當等待一個內建的非同步操作時,內建的非同步操作將會捕獲當前的上下文context,然後會把它應用到這個非同步方法的剩餘部分。

那什麼是context呢?

簡單理解如下:

1.如果你正處於一個UI執行緒,那麼它就是一個UI上下文。

2.如果你正在響應一個ASP.NET請求,那它就是一個ASP.NET請求上下文。

3.否則,它通常是一個執行緒池上下文。

複雜理解如下:

1.如果SynchronizationContext.Current不是null,那麼它就是當前的SynchronizationContext.Current。(UI 和ASP.NET請求上下文都是SynchronizationContext 物件)

2.否則,它就是當前的TaskScheduler(TaskScheduler.Default是執行緒池上下文)。

在真實世界中,這意味著什麼呢?首先,捕獲(和儲存)UI/ASP.NET上下文是透明的,即不可見的。

// WinForms 例子(對於 WPF同樣有效).
private async void DownloadFileButton_Click(object sender,EventArgs e)
{
    // 當我們非同步等待的時候,UI執行緒沒有被檔案下載阻塞。
    await DownloadFileAsync(fileNameTextBox.Text);// 因為我們還處於UI執行緒上,所以還可以直接訪問UI元素。
    resultTextBox.Text="File downloaded!";
}
  // ASP.NET 例子
protected async void MyButton_Click(object sender,EventArgs e)
{
     // 當我們非同步等待的時候,ASP.NET執行緒沒有被檔案下載阻塞。
    // 當我們等待的時候,就可以使用當前執行緒處理其他的請求。
    await DownloadFileAsync(...);// 既然我們還在ASP.NET上下文上,我們就可以訪問當前請求。
   //雖然實際上我們可能在其他的執行緒上,但是我們仍有相同的ASP.NET請求上下文。
   Response.Write("File downloaded!");
}
 

Avoiding Context:避免上下文

多數時候,你不需要同步返回到main上下文。記住:大多數非同步方法被組合設計--他們等待其他多個操作,每一個操作本身代表一個非同步操作(每個非同步操作又可以被多個非同步操作組合)。

這種情況下,你想要告訴非同步者(awaiter)通過呼叫ConfigureAwait並且傳遞false不要捕捉當前上下文,比如:

private async TaskDownloadFileAsync(string fileName)
        {
            // 使用HttpClient或其他東西來下載檔案內容。
            var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false);// 注意因為 ConfigureAwait(false),此時我們就不在原始的上下文了.
            // 取而代之的是,我們正執行線上程池上.
            // 將檔案內容寫入磁碟檔案.
            await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false);
        }
        // WinForms和 WPF均適用.這次沒有呼叫ConfigureAwait(false) 
        private async void DownloadFileButton_Click(object sender, EventArgs e)
        {
            // 當我們非同步等待的時候,ASP.NET執行緒沒有被檔案下載阻塞。
            await DownloadFileAsync(fileNameTextBox.Text);// 因為我們還處於UI執行緒上,所以還可以直接訪問UI元素。
            resultTextBox.Text = "File downloaded!";
        }

這個例子最重要的值得注意的是每一級的非同步方法的呼叫都有自己的上下文。DownloadFileButton_Click開始是在UI執行緒上,然後呼叫了DownloadFileContentAsync,DownloadFileContentAsync一開始也在這個UI執行緒上。然後通過呼叫ConfigureAwait(false)跳出當前上下文。DownloadFileContentAsync剩餘部分就執行線上程池上下文中了。然而,當DownloadFileContentAsync執行完成後,當DownloadFileButton_Click 恢復時,它(DownloadFileButton_Click )再次恢復到當前UI執行緒中了。

好的經驗是:除非你知道你確實需要這個上下文,你可以使用ConfigureAwait(false)。

Async Composition 非同步組合

至此,我們僅僅考慮了序列組合:一個非同步方法一次等待一個操作。開始多個操作並且等待這些操作的一個(或全部)完成的情況也是可能的。

可以通過開始這些操作而不等待等待它們,直到以後再等待它們來達到這個目的。

public async Task DoOperationsConcurrentlyAsync()
{
  Task[] tasks = new Task[3];
  tasks[0] = DoOperation0Async();
  tasks[1] = DoOperation1Async();
  tasks[2] = DoOperation2Async();

  //此時,所有的3個任務在同一時間執行。

  // 現在,我們等待它們。
  await Task.WhenAll(tasks);
}

public async Task<int> GetFirstToRespondAsync()
{
  // 呼叫2個web服務; 獲取第一個響應.
  Task<int>[] tasks = new[] { WebService1Async(), WebService2Async() };

  // 等待第一個任務響應.
  Task<int> firstTask = await Task.WhenAny(tasks);

  // Return結果.
  return await firstTask;
}

通過使用併發組合 (Task.WhenAll 或Task.WhenAny),可以執行簡單的併發操作。也可以伴隨Task.Run來使用這些方法來做一些簡單的平行計算。

然而,這不是任務並行庫(TPL)的替代品,任何高階的CPU集中的並行操作應該使用TPL來完成。

Guidelines 指南

有興趣的朋友可以讀一下這篇文章 Task-based Asynchronous Pattern (TAP) document(連結地址:

http://www.microsoft.com/en-us/download/details.aspx?id=19957),是英文的,包括API設計的指導和具體使用async和await的場景,需要的話支援一下,我給大家翻譯過來分享一下,謝謝。

Old

New

Description

task.Wait await task 等待任務完成
task.Result await task 獲取完成任務的結果
Task.WaitAny await Task.WhenAny 等待任務集合中的任何一個完成
Task.WaitAll await Task.WhenAll 等待所有任務都完成
Thread.Sleep await Task.Delay 等待一段時間
Task constructor Task.Run or TaskFactory.StartNew 建立一個基於程式碼的任務

 

Next Steps 下一步

我這裡還有一篇文章非同步程式設計最佳實踐,進一步解釋了“避免async void”,總是”async”和”配置上下文”的指南。

微軟的官文也相當不錯official MSDN documentation。非同步團隊也釋出了很多關於非同步/等待的常見問題,這是繼續學習非同步的絕好去處。

文章翻譯來源:http://blog.stephencleary.com/2012/02/async-and-await.html

 

 


相關文章