您可以使用 Async
和 Await
關鍵詞,更輕鬆地撰寫和維護異步程式。 不過,如果您不瞭解程序的運作方式,結果可能會讓您大吃一驚。 本主題會透過一個簡單的非同步程式來追蹤控制流,以便展示每當控制從一個方法轉移到另一個方法時,哪個資訊被傳遞。
備註
和 Async
Await
關鍵詞是在 Visual Studio 2012 中引進的。
一般而言,您可以使用 Async 修飾詞標記包含異步程式代碼的方法。 在以異步修飾詞標示的方法中,您可以使用 Await (Visual Basic) 運算符來指定方法暫停以等候呼叫的異步進程完成的位置。 如需詳細資訊,請參閱使用 Async 和 Await 進行異步程式設計(Visual Basic)。
下列範例會使用異步方法,將指定網站的內容下載為字串,並顯示字串的長度。 此範例包含下列兩種方法。
startButton_Click
,其會呼叫AccessTheWebAsync
並顯示結果。AccessTheWebAsync
,它會將網站的內容下載為字串,並傳回字串的長度。AccessTheWebAsync
使用異步 HttpClient 方法 GetStringAsync(String),下載內容。
編號顯示行會出現在整個程式中的戰略點,以協助您瞭解程序執行的方式,並說明標記的每個點會發生什麼事。 顯示行會標示為 「ONE」 到 「SIX」。標籤代表程式到達這些程式代碼行的順序。
下列程式代碼顯示程式的大綱。
Class MainWindow
Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click
' ONE
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
' FOUR
Dim contentLength As Integer = Await getLengthTask
' SIX
ResultsTextBox.Text &=
vbCrLf & $"Length of the downloaded string: {contentLength}." & vbCrLf
End Sub
Async Function AccessTheWebAsync() As Task(Of Integer)
' TWO
Dim client As HttpClient = New HttpClient()
Dim getStringTask As Task(Of String) =
client.GetStringAsync("https://learn.microsoft.com")
' THREE
Dim urlContents As String = Await getStringTask
' FIVE
Return urlContents.Length
End Function
End Class
每個已標記的位置 「ONE」 到 「SIX」 會顯示程式目前狀態的相關信息。 會產生下列輸出:
ONE: Entering startButton_Click.
Calling AccessTheWebAsync.
TWO: Entering AccessTheWebAsync.
Calling HttpClient.GetStringAsync.
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
Length of the downloaded string: 33946.
設定程式
您可以從 MSDN 下載本主題所使用的程式代碼,也可以自行建置。
備註
若要執行此範例,您必須在計算機上安裝Visual Studio 2012或更新版本,以及已安裝 .NET Framework 4.5 或更新版本。
下載程式
您可以從 異步程式中的異步範例:控制流程下載本主題的應用程式。 下列步驟會開啟並執行程式。
解壓縮下載的檔案,然後啟動Visual Studio。
在功能表欄上,選擇 [ 檔案]、[ 開啟]、[ 專案/方案]。
流覽至保存解壓縮範例程式代碼的資料夾,開啟方案 (.sln) 檔案,然後選擇 F5 索引鍵來建置並執行專案。
自行建置程式
下列 Windows Presentation Foundation (WPF) 專案包含本主題的程式碼範例。
若要執行專案,請執行下列步驟:
啟動 Visual Studio。
在功能表欄上,選擇 [ 檔案]、[ 新增]、[ 專案]。
[ 新增專案 ] 對話框隨即開啟。
在 [ 已安裝的範本 ] 窗格中,選擇 [Visual Basic],然後從專案類型清單中選擇 [WPF 應用程式 ]。
輸入
AsyncTracer
作為專案的名稱,然後選擇 [ 確定] 按鈕。新的專案會出現在 [方案總管] 中。
在 Visual Studio Code 編輯器中,選擇 MainWindow.xaml 索引標籤 。
如果看不到索引標籤,請在 [方案總管] 中開啟 MainWindow.xaml 的快捷方式選單,然後選擇 [檢視程式碼]。
在MainWindow.xaml的 XAML 檢視中,將程式代碼取代為下列程序代碼。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MainWindow" Title="Control Flow Trace" Height="350" Width="525"> <Grid> <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="221,10,0,0" VerticalAlignment="Top" Width="75"/> <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="510" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" d:LayoutOverrides="HorizontalMargin"/> </Grid> </Window>
包含文字框和按鈕的簡單視窗會出現在MainWindow.xaml 的設計 檢視中。
加入 System.Net.Http 的參考。
在 [方案總管] 中,開啟MainWindow.xaml.vb的快捷方式功能表,然後選擇 [ 檢視程序代碼]。
在 MainWindow.xaml.vb 中,將程式代碼取代為下列程序代碼。
' Add an Imports statement and a reference for System.Net.Http. Imports System.Net.Http Class MainWindow Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs) Handles StartButton.Click ' The display lines in the example lead you through the control shifts. ResultsTextBox.Text &= "ONE: Entering StartButton_Click." & vbCrLf & " Calling AccessTheWebAsync." & vbCrLf Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync() ResultsTextBox.Text &= vbCrLf & "FOUR: Back in StartButton_Click." & vbCrLf & " Task getLengthTask is started." & vbCrLf & " About to await getLengthTask -- no caller to return to." & vbCrLf Dim contentLength As Integer = Await getLengthTask ResultsTextBox.Text &= vbCrLf & "SIX: Back in StartButton_Click." & vbCrLf & " Task getLengthTask is finished." & vbCrLf & " Result from AccessTheWebAsync is stored in contentLength." & vbCrLf & " About to display contentLength and exit." & vbCrLf ResultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength) End Sub Async Function AccessTheWebAsync() As Task(Of Integer) ResultsTextBox.Text &= vbCrLf & "TWO: Entering AccessTheWebAsync." ' Declare an HttpClient object. Dim client As HttpClient = New HttpClient() ResultsTextBox.Text &= vbCrLf & " Calling HttpClient.GetStringAsync." & vbCrLf ' GetStringAsync returns a Task(Of String). Dim getStringTask As Task(Of String) = client.GetStringAsync("https://learn.microsoft.com") ResultsTextBox.Text &= vbCrLf & "THREE: Back in AccessTheWebAsync." & vbCrLf & " Task getStringTask is started." ' AccessTheWebAsync can continue to work until getStringTask is awaited. ResultsTextBox.Text &= vbCrLf & " About to await getStringTask & return a Task(Of Integer) to StartButton_Click." & vbCrLf ' Retrieve the website contents when task is complete. Dim urlContents As String = Await getStringTask ResultsTextBox.Text &= vbCrLf & "FIVE: Back in AccessTheWebAsync." & vbCrLf & " Task getStringTask is complete." & vbCrLf & " Processing the return statement." & vbCrLf & " Exiting from AccessTheWebAsync." & vbCrLf Return urlContents.Length End Function End Class
選擇 F5 鍵以執行程式,然後選擇 [ 開始 ] 按鈕。
應該會出現下列輸出:
ONE: Entering startButton_Click. Calling AccessTheWebAsync. TWO: Entering AccessTheWebAsync. Calling HttpClient.GetStringAsync. THREE: Back in AccessTheWebAsync. Task getStringTask is started. About to await getStringTask & return a Task<int> to startButton_Click. FOUR: Back in startButton_Click. Task getLengthTask is started. About to await getLengthTask -- no caller to return to. FIVE: Back in AccessTheWebAsync. Task getStringTask is complete. Processing the return statement. Exiting from AccessTheWebAsync. SIX: Back in startButton_Click. Task getLengthTask is finished. Result from AccessTheWebAsync is stored in contentLength. About to display contentLength and exit. Length of the downloaded string: 33946.
追蹤程式
步驟一和二
前兩個顯示行會將路徑追蹤為 startButton_Click
呼叫 AccessTheWebAsync
,並 AccessTheWebAsync
呼叫異步 HttpClient 方法 GetStringAsync(String)。 下圖概述方法之間的呼叫順序。
AccessTheWebAsync
和 client.GetStringAsync
的回傳型別皆為 Task<TResult>。 針對 AccessTheWebAsync
,TResult 是整數。 針對 GetStringAsync
,TResult 是字串。 如需異步方法傳回型別的詳細資訊,請參閱 異步傳回型別 (Visual Basic) 。
當控制權轉移回呼叫端時,返回任務的異步方法會傳回任務實例。 當呼叫的方法中遇到運算符 Await
或當呼叫的方法結束時,控制會從異步方法返回至調用者。 標示為「THREE」到「SIX」的顯示行會追蹤這個過程的這一部分。
步驟三
在 中 AccessTheWebAsync
,會呼叫異步方法 GetStringAsync(String) 以下載目標網頁的內容。 當 client.GetStringAsync
傳回時,控制權會從 AccessTheWebAsync
返回至 client.GetStringAsync
。
方法 client.GetStringAsync
傳回一個字串的工作,該工作被指派給 getStringTask
中的 AccessTheWebAsync
變數。 範例程式中的下一行會顯示 對 client.GetStringAsync
和 指派的呼叫。
Dim getStringTask As Task(Of String) = client.GetStringAsync("https://learn.microsoft.com")
您可以將這項任務視為由 client.GetStringAsync
承諾最終產生實際字串。 同時,如果 AccessTheWebAsync
有不需要依賴client.GetStringAsync
提供的承諾字串的工作,那麼 AccessTheWebAsync
可以在 等待的同時繼續進行這些工作。 在此範例中,標示為 「THREE」 的下列幾行輸出,代表執行獨立工作的機會
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
下列語句會在等候 AccessTheWebAsync
時暫停 getStringTask
的進度。
Dim urlContents As String = Await getStringTask
下圖顯示從 client.GetStringAsync
到指派給 getStringTask
的控制流程,以及從建立 getStringTask
到應用 Await 運算子的過程。
await 表達式會 AccessTheWebAsync
暫停,直到 client.GetStringAsync
傳回為止。 目前,控制權會傳回給呼叫 AccessTheWebAsync
startButton_Click
的一方。
備註
一般而言,您會立即等候對異步方法的呼叫。 例如,下列指派可以取代先前建立的程式碼,然後等候 getStringTask
: Dim urlContents As String = Await client.GetStringAsync("https://learn.microsoft.com")
本主題稍後會套用 await 運算子,以容納透過程式標記控制流程的輸出行。
步驟四
宣告的傳回型別 AccessTheWebAsync
是 Task(Of Integer)
。 因此,當 AccessTheWebAsync
被暫停時,它會將整數任務傳回給 startButton_Click
。 您應該瞭解傳回的工作不是 getStringTask
。 傳回的工作是整數的新工作,代表在暫停的方法中尚未完成的任務。AccessTheWebAsync
這項工作是AccessTheWebAsync
的承諾,將在工作完成時產生一個整數。
下列語句會將此工作指派給 getLengthTask
變數。
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
如同 AccessTheWebAsync
中,startButton_Click
在等候異步工作(getLengthTask
)之前,可以繼續進行不依賴於該工作的其他工作。 下列輸出行代表該工作:
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
當在等待startButton_Click
時,getLengthTask
的進度會暫停。 下列分派語句會暫停 startButton_Click
,直到 AccessTheWebAsync
完成為止。
Dim contentLength As Integer = Await getLengthTask
在下圖中,箭號會顯示從 await 運算式 AccessTheWebAsync
到 值 getLengthTask
指派的控制流程,後面接著在 中 startButton_Click
正常處理,直到 getLengthTask
等候為止。
步驟五
當 client.GetStringAsync
發出已完成的訊號時,AccessTheWebAsync
中的處理會從暫停中釋放,並可以繼續執行超過 await 語句。 下列幾行輸出代表繼續處理:
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
return 語句urlContents.Length
的運算元會儲存在由AccessTheWebAsync
傳回的任務中。 await 表達式會從 getLengthTask
中 startButton_Click
擷取該值。
下圖顯示在client.GetStringAsync
(和getStringTask
)完成之後的控制權傳輸。
AccessTheWebAsync
會執行至完成,控制權會傳回給 startButton_Click
,而 startButton_Click
正在等候完成。
步驟六
當 AccessTheWebAsync
發出已完成的訊號時,處理可以繼續超過 startButton_Async
中的 await 語句。 事實上,程式沒有什麼可做的事。
下列幾行輸出代表startButton_Async
中的恢復處理:
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
await 表達式會從 getLengthTask
中 AccessTheWebAsync
傳回語句的作數整數值擷取。 下列語句會將該值指派給 contentLength
變數。
Dim contentLength As Integer = Await getLengthTask
下圖顯示從 AccessTheWebAsync
傳回控制權到 startButton_Click
。