使用 Async 和 Await 進行非同步程式設計 (Visual Basic)
您可以使用非同步程式設計,避免發生效能瓶頸並增強應用程式的整體回應性。 不過,撰寫非同步應用程式的傳統技術可能很複雜,因而難以撰寫、偵錯和維護。
Visual Studio 2012 引進了一種簡化的方法 (非同步程式設計),來運用 .NET Framework 4.5 和更新版本以及 Windows 執行階段中的非同步支援。 編譯器會代替開發人員處理過去經常要處理的困難工作,而您的應用程式仍保有類似同步程式碼的邏輯結構。 因此,您可以輕鬆擁有非同步程式設計的所有優點。
本主題提供使用非同步程式設計的時機和使用方式的概觀,並加入包含詳細資料及範例的支援主題連結。
非同步可改善回應性
非同步對於可能在像是應用程式存取 Web 時會進行封鎖的活動而言相當重要。 存取 Web 資源的速度有時會變慢或延遲。 如果這類活動在同步處理序中遭到封鎖,整個應用程式就必須等候。 在非同步處理序中,應用程式可以繼續處理其他與 Web 資源不相關的工作,直到可能的封鎖工作完成。
下表顯示非同步程式設計一般會改善回應速度的部分。 從 .NET Framework 4.5 和 Windows 執行階段列出的 API 包含支援非同步程式設計的方法。
應用程式區域 | 包含非同步方法的支援 API |
---|---|
Web 存取 | HttpClient、SyndicationClient |
處理檔案 | StorageFile, StreamWriter, StreamReader, XmlReader |
使用映像 | MediaCapture、BitmapEncoder、BitmapDecoder |
WCF 程式設計 | 同步和非同步作業 |
非同步對於存取 UI 執行緒的應用程式而言確實特別有用,因為所有 UI 相關活動通常都會共用一個執行緒。 如果同步應用程式中有任何處理序遭到封鎖,所有處理序都會遭到封鎖。 您的應用程式會停止回應,而且您可能會認為應用程式失敗,但實際上只是在等候。
當您使用非同步方法時,應用程式會繼續回應 UI。 例如,您可以調整視窗大小或將視窗縮到最小,如果不想要等待應用程式完成,也可以將它關閉。
非同步方法會在設計非同步作業時,於選項清單中加入自動傳輸的對等項目讓您選擇。 也就是說,除了擁有傳統非同步程式設計的所有優點之外,開發人員所需投入的時間也大為減少。
非同步方法比較容易撰寫
Visual Basic 中的 Async 和 Await 關鍵字都是非同步程式設計的核心。 您可以使用這兩個關鍵字,使用 .NET Framework 或 Windows 執行階段中的資源來建立非同步方法,幾乎就像建立同步方法一樣容易。 使用 Async
和 Await
定義的非同步方法就稱為非同步方法。
下列範例將示範非同步方法。 程式碼中的一切對您而言應該幾乎完全熟悉。 註解會標註您加入以建立非同步的功能。
您可以在本主題結尾找到完整的 Windows Presentation Foundation (WPF) 範例檔案,並且可從非同步範例:<使用 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)。
- 如果方法沒有 return 陳述式或是 return 陳述式沒有運算元,則為 Task。
- 如果您撰寫的是非同步事件處理常式,則為 Sub。
如需詳細資訊,請參閱本主題後段的<傳回型別和參數>。
方法通常至少包含一個 await 運算式,表示方法在等候的非同步作業完成後才能繼續的點。 此時,方法已暫停,而且控制權返回到方法的呼叫端。 本主題的下一節將說明暫停點會發生什麼情況。
在非同步方法中,您會使用提供的關鍵字和類型表示您想要執行的工作,而編譯器會完成其餘的部分,包括追蹤控制權返回已暫停方法中的等候點時必須進行的作業。 某些常式處理序像是迴圈和例外狀況處理,在傳統非同步程式碼中可能不容易處理。 在非同步方法中,您可以像在同步方案中一樣撰寫這些項目,如此就可以解決這個問題了。
如需舊版 .NET Framework 中非同步的詳細資訊,請參閱 TPL 和傳統 .NET Framework 非同步程式設計。
非同步方法中執行了哪些工作
在非同步程式設計中要了解的最重要事情,就是控制流程如何在方法之間移動。 下圖將引導您了解整個程序:
圖中的數字對應下列步驟:
事件處理常式會呼叫並等候
AccessTheWebAsync
非同步方法。AccessTheWebAsync
會建立 HttpClient 執行個體並呼叫 GetStringAsync 非同步方法,將網站的內容當做字串下載。GetStringAsync
中發生了導致進度暫停的一些狀況。 可能必須等待網站下載或其他封鎖活動。 為了避免封鎖資源,GetStringAsync
會將控制權遞交 (Yield) 給它的呼叫端AccessTheWebAsync
。GetStringAsync
會傳回 Task(Of TResult) (其中 TResult 是字串),而AccessTheWebAsync
則會將工作指派給getStringTask
變數。 工作代表對GetStringAsync
之呼叫的進行中程序,並承諾會在工作完成時產生實際字串值。因為尚未等候
getStringTask
,所以AccessTheWebAsync
可以繼續進行其他不相依於GetStringAsync
之最終結果的其他工作。 這項工作是由對同步方法DoIndependentWork
的呼叫來表示。DoIndependentWork
是完成其工作並傳回其呼叫端的同步方法。AccessTheWebAsync
已完成所有可處理的工作,但未取得來自getStringTask
的結果。AccessTheWebAsync
接著要計算和傳回下載字串的長度,但是方法必須等到有字串時才能計算該值。因此,
AccessTheWebAsync
會使用 await 運算子暫停其進度,並將控制權遞交 (Yield) 給呼叫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)。
應用程式開發介面非同步方法
您可能會想知道哪裡可以找到支援非同步程式設計的方法,例如 GetStringAsync
。 .NET Framework 4.5 或更新版本包含許多使用 Async
和 Await
的成員。 您也可以藉由附加至成員名稱的 "Async" 尾碼和傳回型別 Task 或 Task(Of TResult) 辨認這些成員。 例如,相對於同步方法 CopyTo、Read 和 Write,System.IO.Stream
類別也包含一些方法,例如 CopyToAsync、ReadAsync 和 WriteAsync。
Windows 執行階段也包含許多您可以在 Windows 應用程式中與 Async
和 Await
搭配使用的方法。 如需詳細資訊和範例方法,請參閱在 C# 或 Visual Basic 中呼叫非同步 API、非同步程式設計 (Windows 執行階段應用程式) 和 WhenAny:銜接 .NET Framework 與 Windows 執行階段。
執行緒
非同步方法主要做為非封鎖作業使用。 當等候的工作正在執行時,非同步方法的 Await
運算式不會封鎖目前的執行緒。 運算式會改為註冊方法的其餘部分做為接續,並將控制權交還給非同步方法的呼叫端。
Async
和 Await
關鍵字不會導致建立其他執行緒。 由於非同步方法不會在本身的執行緒上執行,因此非同步方法不需要多執行緒。 方法會在目前的同步處理內容執行,而且只有在方法為作用中時才會在執行緒上花費時間。 您可以使用 Task.Run 將受限於 CPU 的工作移到背景執行緒,但是背景執行緒無法協助處理正在等待結果產生的處理序。
非同步程式設計的非同步方法幾乎是所有案例的現有方法當中較好的方法。 特別是此方法比 I/O 繫結作業的 BackgroundWorker 更好,因為程式碼較簡單,而您也不需要防範競爭條件。 與 Task.Run 結合時,非同步程式設計會比受限於 CPU 之作業的 BackgroundWorker 還要好,因為非同步程式設計會將執行程式碼的協調工作細節,從 Task.Run
傳輸至執行緒集區的工作中分出來。
Async 和 Await
如果您使用 Async 修飾詞來將方法指定為非同步方法,就會啟用下列兩項功能。
標記的非同步方法可以使用 Await 來指定暫停點。 await 運算子會告知編譯器,非同步方法只有在等候的非同步處理序完成後,才能繼續通過該點。 同時,控制權會返回非同步方法的呼叫端。
非同步方法在
Await
運算式上暫停時,並不構成從方法中退出,而Finally
區塊也不會執行。標記的非同步方法本身可以做為其呼叫方法的等候目標。
非同步方法中通常會出現一或多次 Await
運算子,但是沒有 Await
運算式也不會造成編譯器錯誤。 如果非同步方法未使用 Await
運算子來標記暫停點,則即使有 Async
修飾元,方法仍會像同步方法一樣執行。 編譯器將對這類方法發出警告。
Async
和 Await
都是內容關鍵字。 如需詳細資訊和範例,請參閱下列主題:
傳回型別和參數
在 .NET Framework 程式設計中,非同步方法通常會傳回 Task 或 Task(Of TResult)。 在非同步方法內,會將 Await
運算子套用到呼叫另一個非同步方法所傳回的工作。
如果方法包含指定 TResult
型別運算元的 Return 陳述式,則請將 Task(Of 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(Of TProgress)
- IAsyncOperationWithProgress(Of TResult, TProgress)
如需詳細資訊和範例,請參閱在 C# 或 Visual Basic 中呼叫非同步 API。
命名慣例
依照慣例,您會將 "Async" 附加至具有 Async
修飾詞的方法名稱。
當事件、基底類別或介面合約採用不同的名稱時,您可以忽略慣例。 例如,您不應該重新命名通用事件處理常式 (像是 Button1_Click
)。
相關主題和範例 (Visual Studio)
完整範例
下列程式碼是本主題討論之 Windows Presentation Foundation (WPF) 應用程式中的 MainWindow.xaml.vb 檔案。 您可以從非同步範例:<使用 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