【第一次這么耐下性子認真寫博客,雖然覺得很認真了,當畢竟是第一次嘛,以后再看肯定覺得很不咋滴的,更何況園子里有那么多的高人和大俠,這篇文章就權當練練手了,熟悉一下用客戶端發表博客了,也希望大家多多照顧新人,這廂有禮了
!】下面正式開始,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
