您可以使用異步程式設計來避免效能瓶頸,並增強應用程式的整體回應性。 不過,撰寫異步應用程式的傳統技術可能很複雜,因此難以撰寫、偵錯和維護。
Visual Studio 2012 引進了簡化的方法,非同步程式設計,可運用 .NET Framework 4.5 和更新版本中的非同步支援,以及 Windows 執行階段。 編譯程式會執行開發人員用來執行的困難工作,而您的應用程式會保留類似同步程式代碼的邏輯結構。 因此,您可以使用一小部分的努力,取得異步程序設計的所有優點。
本主題提供何時及如何使用異步程式設計的概觀,並包含包含詳細數據和範例的支持主題連結。
Async 可改善回應性
Asynchrony 對於可能封鎖的活動而言非常重要,例如當您的應用程式存取 Web 時。 有時 Web 資源的存取速度很慢或延遲。 如果這類活動在同步進程中遭到封鎖,整個應用程式必須等候。 在異步程式中,應用程式可以繼續執行其他不相依於 Web 資源的工作,直到潛在的封鎖工作完成為止。
下表顯示異步程序設計可改善回應性的一般區域。 .NET Framework 4.5 和 Windows 運行時間所列的 API 包含支援異步程序設計的方法。
應用程式區域 | 支援包含異步方法的 API |
---|---|
網路存取 | HttpClient、SyndicationClient |
處理檔案 | StorageFile、StreamWriter、StreamReader、XmlReader |
處理圖像 | MediaCapture、BitmapEncoder、BitmapDecoder |
WCF 程式設計 | 同步和異步操作 |
Asynchrony 對於存取 UI 線程的應用程式來說特別有價值,因為所有 UI 相關活動通常會共用一個線程。 如果同步應用程式中有任何進程遭到封鎖,則會封鎖所有進程。 您的應用程式可能會停止回應,而您可能會誤以為它已經失敗,其實它只是正在等待。
當您使用異步方法時,應用程式會繼續回應 UI。 例如,您可以調整或最小化視窗的大小,或者如果您不想等待應用程式完成,可以關閉應用程式。
異步方法相當於為設計異步作業時可供選擇的選項列表新增自動變速器。 也就是說,您會獲得傳統異步程序設計的所有優點,但開發人員所付出的努力要少得多。
異步方法更容易撰寫
Visual Basic 中的 Async 和 Await 關鍵詞是異步程序設計的核心。 藉由使用這兩個關鍵字,您可以利用 .NET Framework 或 Windows 執行階段中的資源,像建立同步方法一樣輕鬆地建立異步方法。 您使用 Async
和 Await
定義的異步方法稱為異步方法。
以下範例,顯示非同步方法。 程式碼中的幾乎所有內容看起來應該都非常熟悉。 批注會指出您新增建立異步所需的功能。
您可以在本主題結尾找到完整的 Windows Presentation Foundation (WPF) 範例檔案,您可以從 Async 範例:從「使用 Async 和 Await 進行異步程式設計」的範例下載範例。
' Three things to note about writing an Async Function:
' - The function has an Async modifier.
' - Its return type is Task or Task(Of T). (See "Return Types" section.)
' - As a matter of convention, its name ends in "Async".
Async Function AccessTheWebAsync() As Task(Of Integer)
Using client As New HttpClient()
' Call and await separately.
' - AccessTheWebAsync can do other things while GetStringAsync is also running.
' - getStringTask stores the task we get from the call to GetStringAsync.
' - Task(Of String) means it is a task which returns a String when it is done.
Dim getStringTask As Task(Of String) =
client.GetStringAsync("https://learn.microsoft.com/dotnet")
' You can do other work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork()
' The Await operator suspends AccessTheWebAsync.
' - AccessTheWebAsync does not continue until getStringTask is complete.
' - Meanwhile, control returns to the caller of AccessTheWebAsync.
' - Control resumes here when getStringTask is complete.
' - The Await operator then retrieves the String result from getStringTask.
Dim urlContents As String = Await getStringTask
' The Return statement specifies an Integer result.
' A method which awaits AccessTheWebAsync receives the Length value.
Return urlContents.Length
End Using
End Function
如果 AccessTheWebAsync
沒有任何可在呼叫 GetStringAsync
和等候完成之間執行的工作,您可以在下列單一語句中呼叫和等候來簡化程序代碼。
Dim urlContents As String = Await client.GetStringAsync()
下列特性摘要說明先前範例如何成為異步方法:
方法簽章包含
Async
修飾詞。依慣例,異步方法的名稱會以 「Async」 後綴結尾。
傳回型態是下列其中一種類型:
- 如果您的方法有一個 return 陳述式,其中運算元的類型為 TResult,則使用 Task(Of TResult)。
- Task 如果您的方法沒有 return 語句,或 return 語句沒有操作數。
- 如果您要撰寫異步事件處理程式,請使用Sub。
如需詳細資訊,請參閱本主題稍後的「返回類型和參數」。
這個方法通常至少包含一個 await 表達式,該表達式會標示出在等候的異步操作完成之前方法不能繼續執行的位置。 同時,方法會暫停,而控件會傳回方法的呼叫端。 本主題的下一節說明暫停點會發生什麼事。
在異步方法中,您可以使用提供的關鍵詞和類型來指出您想要執行的動作,而編譯程式會執行其餘作業,包括追蹤當控件回到暫停方法中的等候點時,必須執行的動作。 某些例行程式,例如迴圈和例外狀況處理,在傳統異步程序代碼中可能很難處理。 在異步方法中,您可以像在同步解決方案中一樣撰寫這些元素,並解決問題。
如需舊版 .NET Framework 中異步的詳細資訊,請參閱 TPL 和傳統 .NET Framework 異步程序設計。
異步方法中會發生什麼事
異步程式開發中最重要的是要理解控制流如何在不同的方法間傳遞。 下圖引導您完成此過程:
圖表中的數字會對應至下列步驟:
事件處理程式會呼叫並等候
AccessTheWebAsync
異步方法。AccessTheWebAsync
會建立 HttpClient 實例,並呼叫 GetStringAsync 異步方法,以字串的形式下載網站的內容。在
GetStringAsync
發生的事件會暫停其進展。 也許它必須等待網站下載或其他封鎖活動。 若要避免封鎖資源,GetStringAsync
將控制權移交給其呼叫端AccessTheWebAsync
。GetStringAsync
會傳回 Task(Of TResult),其中 TResult 是字串,並將AccessTheWebAsync
工作指派給getStringTask
變數。 工作代表呼叫GetStringAsync
的持續程序,承諾在工作完成時產生實際字串值。因為
getStringTask
尚未被等待,AccessTheWebAsync
可以繼續執行其他不依賴GetStringAsync
的最終結果的工作。 該工作是由對同步方法DoIndependentWork
的呼叫表示。DoIndependentWork
是同步方法,可執行其工作並傳回其呼叫端。AccessTheWebAsync
已經用盡了可以執行的工作,除非得到getStringTask
的結果。AccessTheWebAsync
next 想要計算並傳回所下載字串的長度,但方法在方法具有字串之前無法計算該值。因此,
AccessTheWebAsync
使用 await 運算符暫停其進度,並將控制權交予呼叫AccessTheWebAsync
的方法。AccessTheWebAsync
傳回Task(Of Integer)
給呼叫者。 該任務承諾會生成一個整數結果,這是下載的字串長度。備註
如果
GetStringAsync
(因此getStringTask
) 在AccessTheWebAsync
等候它之前完成,那麼控制權將保留在AccessTheWebAsync
。 如果呼叫的異步程式 (AccessTheWebAsync
) 已完成,而且 AccessTheWebSync 不必等待最終結果,暫停再返回getStringTask
的費用就會浪費。在呼叫端內(此範例中的事件處理程式),處理模式會繼續。 呼叫端可能會在等待結果
AccessTheWebAsync
之前,執行其他不依賴於該結果的工作,或者呼叫端可能會立即等待該結果。 事件處理程式正在等候AccessTheWebAsync
,並AccessTheWebAsync
正在等候GetStringAsync
。GetStringAsync
會完成併產生字串結果。 呼叫GetStringAsync
不會以您預期的方式傳回字串結果。 (請記住,方法已在步驟 3 中傳回工作。相反地,字串結果會儲存在表示方法完成的工作中,getStringTask
。 await 運算符會從getStringTask
擷取結果。 指定語句會將擷取的結果指派給urlContents
。當 有字串結果時
AccessTheWebAsync
,方法可以計算字串的長度。 接著,AccessTheWebAsync
的工作也會完成,而等候的事件處理程式可以繼續。 在主題結尾的完整範例中,您可以確認事件處理程式會擷取並列印長度結果的值。
如果您不熟悉異步程序設計,請花一分鐘的時間考慮同步和異步行為之間的差異。 同步方法會在工作完成時傳回 (步驟 5),但異步方法會在工作暫停時傳回工作值(步驟 3 和 6)。 當異步方法最終完成其工作時,工作會標示為已完成,如果有任何,則結果會儲存在工作中。
如需控制流程的詳細資訊,請參閱異步程式中的控制流程(Visual Basic)。
API 異步方法
您可能想知道在哪裡可以找到支援非同步程式設計的方法,例如 GetStringAsync
。 .NET Framework 4.5 或更高版本包含許多使用 Async
和 Await
的成員。 您可以透過成員名稱附加的「Async」後綴和返回類型為 Task 或 Task(Of TResult),來識別這些成員。 例如,類別System.IO.Stream
包含方法CopyToAsync、ReadAsync 和 WriteAsync,以及同步方法CopyTo、Read 和 Write。
Windows 執行環境也包含許多方法,您可以在 Windows 應用程式中搭配 Async
和 Await
使用。 如需詳細資訊和範例方法,請參閱 在 C# 或 Visual Basic 中呼叫異步 API、 異步程式設計(Windows 運行時間應用程式)和 WhenAny:橋接 .NET Framework 與 Windows 運行時間。
線程
非同步方法旨在進行不阻塞的操作。
Await
異步方法中的表達式不會在等候的工作執行時封鎖目前的線程。 相反地,表達式會將方法的其餘部分註冊為接續,並將控制權傳回至異步方法的呼叫端。
Async
和 Await
關鍵詞不會造成其他線程建立。 異步方法不需要多線程處理,因為異步方法不會在其自己的線程上執行。 方法會在目前的同步處理內容上執行,而且只有在方法處於使用中狀態時,才會在線程上使用時間。 您可以使用 Task.Run 將 CPU 密集型工作移至背景執行緒,但背景執行緒無法協助處理只是等待結果可用的程序。
幾乎在每種情況下,基於 async 的非同步程式設計方法都比現有的方法更可取。 特別是,這種方法比 BackgroundWorker I/O 系結作業更好,因為程式代碼比較簡單,您不需要防範競爭條件。 與 Task.Run結合 時,異步程序設計比 BackgroundWorker CPU 系結作業更好,因為異步程式設計會將執行程式碼的協調詳細數據與傳送至線程集區的工作 Task.Run
分開。
Async 和 Await
如果您使用 Async 修飾詞來指定方法是異步方法,您可以啟用下列兩項功能。
標示的異步方法可以使用 Await 來指定暫停點。 await 運算符會告訴編譯程式,在等候的異步程式完成之前,異步方法無法繼續超過該點。 同時,控制權會傳回至異步方法的呼叫者。
表達式中
Await
異步方法的暫停不會構成方法的結束,而且Finally
區塊不會執行。標記的異步方法本身可由呼叫它的方法等候。
異步方法通常包含一或多個運算子的 Await
出現次數,但缺少 Await
表達式並不會造成編譯程序錯誤。 如果異步方法不使用 Await
運算符來標記暫停點,則該方法會像同步方法一樣執行,不論使用 Async
修飾詞。 編譯程式會發出這類方法的警告。
Async
和 Await
是內容關鍵詞。 如需詳細資訊和範例,請參閱下列主題:
傳回類型和參數
在 .NET Framework 程序設計中,異步方法通常會傳回Task或Task(Of TResult)。 在異步方法內, Await
運算符會套用至從呼叫另一個異步方法傳回的工作。
如果方法包含指定某類型運算元的 Return 語句,您可以指定 Task(Of TResult) 作為返回類型TResult
。
如果方法沒有 return 語句,或者包含不返回運算元的 return 語句,您可以使用 Task
作為傳回型別。
下列範例示範如何宣告並呼叫傳回 Task(Of TResult) 或 Task的方法:
' Signature specifies Task(Of Integer)
Async Function TaskOfTResult_MethodAsync() As Task(Of Integer)
Dim hours As Integer
' . . .
' Return statement specifies an integer result.
Return hours
End Function
' Calls to TaskOfTResult_MethodAsync
Dim returnedTaskTResult As Task(Of Integer) = TaskOfTResult_MethodAsync()
Dim intResult As Integer = Await returnedTaskTResult
' or, in a single statement
Dim intResult As Integer = Await TaskOfTResult_MethodAsync()
' Signature specifies Task
Async Function Task_MethodAsync() As Task
' . . .
' The method has no return statement.
End Function
' Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync()
Await returnedTask
' or, in a single statement
Await Task_MethodAsync()
每個傳回的工作都代表進行中的工作。 任務包含有關異步進程狀態的信息,最後結果要么是進程的最終結果,要么是在進程不成功時出現的例外狀況。
異步方法也可以是 Sub
方法。 此傳回型別主要用於需要傳回型別的事件處理程式定義。 異步事件處理程式通常做為異步程式的起點。
無法等候的異步 Sub
程序,呼叫端無法捕捉方法擲回的任何例外狀況。
異步方法無法宣告 ByRef 參數,但方法可以呼叫具有這類參數的方法。
如需詳細資訊和範例,請參閱 異步傳回型別 (Visual Basic) 。 如需如何在非同步方法中捕捉例外狀況的詳細資訊,請參閱 Try...Catch...Finally 語句。
在 Windows 執行階段程式設計中的非同步 API 具有以下其中一種傳回類型,這些類型類似於任務:
- IAsyncOperation(Of TResult),對應至 Task(Of TResult)
- IAsyncAction,對應至 Task
- IAsyncActionWithProgress(TProgress 的)
- IAsyncOperationWithProgress(Of TResult, TProgress)
如需詳細資訊和範例,請參閱 在 C# 或 Visual Basic 中呼叫異步 API。
命名慣例
依照慣例,您會將 「Async」 附加至具有 Async
修飾詞的方法名稱。
您可以忽略事件、基類或介面合約建議不同名稱的慣例。 例如,您不應該重新命名常見的事件處理程式,例如 Button1_Click
。
相關主題和範例 (Visual Studio)
完整範例
下列程式代碼是本主題討論的 Windows Presentation Foundation (WPF) 應用程式中MainWindow.xaml.vb檔案。 您可以從 Async 範例下載範例:「使用 Async 和 Await 進行異步程式設計」的範例。
Imports System.Net.Http
' Example that demonstrates Asynchronous Programming with Async and Await.
' It uses HttpClient.GetStringAsync to download the contents of a website.
' Sample Output:
' Working . . . . . . .
'
' Length of the downloaded string: 39678.
Class MainWindow
' Mark the event handler with Async so you can use Await in it.
Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)
' Call and await immediately.
' StartButton_Click suspends until AccessTheWebAsync is done.
Dim contentLength As Integer = Await AccessTheWebAsync()
ResultsTextBox.Text &= $"{vbCrLf}Length of the downloaded string: {contentLength}.{vbCrLf}"
End Sub
' Three things to note about writing an Async Function:
' - The function has an Async modifier.
' - Its return type is Task or Task(Of T). (See "Return Types" section.)
' - As a matter of convention, its name ends in "Async".
Async Function AccessTheWebAsync() As Task(Of Integer)
Using client As New HttpClient()
' Call and await separately.
' - AccessTheWebAsync can do other things while GetStringAsync is also running.
' - getStringTask stores the task we get from the call to GetStringAsync.
' - Task(Of String) means it is a task which returns a String when it is done.
Dim getStringTask As Task(Of String) =
client.GetStringAsync("https://learn.microsoft.com/dotnet")
' You can do other work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork()
' The Await operator suspends AccessTheWebAsync.
' - AccessTheWebAsync does not continue until getStringTask is complete.
' - Meanwhile, control returns to the caller of AccessTheWebAsync.
' - Control resumes here when getStringTask is complete.
' - The Await operator then retrieves the String result from getStringTask.
Dim urlContents As String = Await getStringTask
' The Return statement specifies an Integer result.
' A method which awaits AccessTheWebAsync receives the Length value.
Return urlContents.Length
End Using
End Function
Sub DoIndependentWork()
ResultsTextBox.Text &= $"Working . . . . . . .{vbCrLf}"
End Sub
End Class