非同步程式中的控制流程 (C# 和 Visual Basic)
您可以使用 Async 和 Await 關鍵字,更輕鬆地撰寫和維護非同步程式。 不過,如果您不知道程式運作的方式,結果可能會讓您驚訝。 這個主題透過簡單的非同步程式追蹤控制流程,顯示控制項從一種方法移到另一種方法,以及每次的資訊傳遞方式。
注意事項 |
---|
Async 與 Await 關鍵字在 Visual Studio 2012 中引入。 |
一般而言,您可以使用 Async (Visual Basic) 或 async (C#) 修飾詞標記包含非同步程式碼的方法。 在以非同步修飾詞標記的方法,您可以使用 Await (Visual Basic) 或 await (C#) 運算子來指定方法暫停的位置,以等候被呼叫的非同步處理序完成。 如需詳細資訊,請參閱使用 Async 和 Await 設計非同步程式 (C# 和 Visual Basic)。
下列範例會使用非同步方法下載指定網站的內容當做字串,並顯示字串的長度。 範例包含下列兩個方法:
startButton_Click,用於呼叫 AccessTheWebAsync 並顯示結果。
AccessTheWebAsync,其將網站內容當做字串下載並傳回字串的長度。 AccessTheWebAsync 會 使用非同步 HttpClient 方法 GetStringAsync(String) 來下載內容。
編號的顯示行會出現在程式中的策略位置,協助您了解程式執行的方式,並解釋每一個標記的位置所發生的狀況。 顯示程式行標示為「一」到「六」。標籤代表程式達到這些程式碼行的順序。
下列程式碼顯示程式的大綱。
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 &=
String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)
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://msdn.microsoft.com")
' THREE
Dim urlContents As String = Await getStringTask
' FIVE
Return urlContents.Length
End Function
End Class
public partial class MainWindow : Window
{
// . . .
private async void startButton_Click(object sender, RoutedEventArgs e)
{
// ONE
Task<int> getLengthTask = AccessTheWebAsync();
// FOUR
int contentLength = await getLengthTask;
// SIX
resultsTextBox.Text +=
String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
}
async Task<int> AccessTheWebAsync()
{
// TWO
HttpClient client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://msdn.microsoft.com");
// THREE
string urlContents = await getStringTask;
// FIVE
return urlContents.Length;
}
}
每個從 "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、Visual Studio 2013、Visual Studio Express 2012、Visual Studio Express 2013 for Windows 或 .NET Framework 4.5 或 4.5.1 安裝在您的電腦上。 |
下載程式
您可以從 Async 範例:在非同步程式的控制流程 下載本主題的應用程式。 下列步驟會開啟並執行程式。
解壓縮下載的檔案,然後啟動 Visual Studio。
在功能表列上,選擇 [檔案]、[開啟]、[專案/方案]。
導覽至存放解壓縮的範例程式碼的資料夾,開啟方案檔 (.sln),然後選擇 F5 鍵建置及執行專案。
自行建立程式
下列 Windows Presentation Foundation (WPF) 專案包含本主題中的程式碼範例。
若要執行專案,請執行下列步驟:
啟動 Visual Studio。
在功能表列上,選擇 [檔案]、[新增]、[專案]。
[新增專案] 對話方塊隨即開啟。
在 [安裝的範本] 窗格中,選擇 [Visual Basic] 或 [Visual C#],從專案類型清單中選擇 [WPF 應用程式]。
輸入 AsyncTracer 做為專案名稱,然後選擇 [確定] 按鈕。
新專案即會出現於 [方案總管] 中。
在 Visual Studio 程式碼編輯器中,選擇 [MainWindow.xaml] 索引標籤。
如果未顯示索引標籤,請在 [方案總管] 中開啟 MainWindow.xaml 的捷徑功能表,然後選擇 [檢視程式碼]。
在 MainWindow.xaml 的 [XAML] 檢視中,將程式碼取代為下列程式碼。
<Window xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://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>
<Window xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="AsyncTracer.MainWindow" Title="Control Flow Trace" Height="350" Width="592"> <Grid> <Button x:Name="startButton" Content="Start
" HorizontalAlignment="Left" Margin="250,10,0,0" VerticalAlignment="Top" Width="75" Height="24" Click="startButton_Click" d:LayoutOverrides="GridBox"/> <TextBox x:Name="resultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Bottom" Width="576" Height="265" FontFamily="Lucida Console" FontSize="10" VerticalScrollBarVisibility="Visible" Grid.ColumnSpan="3"/> </Grid> </Window>
包含文字方塊和按鈕的簡單視窗會出現在 MainWindow.xaml 的 [設計] 檢視中。
加入 System.Net.Http 的參考。
在 [方案總管] 中,開啟 MainWindow.xaml.vb 或 MainWindow.xaml.cs 的捷徑功能表,然後選擇 [檢視程式碼]。
在 MainWindow.xaml.vb 或 MainWindow.xaml.cs 中,將程式碼取代為下列程式碼。
' 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://msdn.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
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; // Add a using directive and a reference for System.Net.Http; using System.Net.Http; namespace AsyncTracer { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void startButton_Click(object sender, RoutedEventArgs e) { // The display lines in the example lead you through the control shifts. resultsTextBox.Text += "ONE: Entering startButton_Click.\r\n" + " Calling AccessTheWebAsync.\r\n"; Task<int> getLengthTask = AccessTheWebAsync(); resultsTextBox.Text += "\r\nFOUR: Back in startButton_Click.\r\n" + " Task getLengthTask is started.\r\n" + " About to await getLengthTask -- no caller to return to.\r\n"; int contentLength = await getLengthTask; resultsTextBox.Text += "\r\nSIX: Back in startButton_Click.\r\n" + " Task getLengthTask is finished.\r\n" + " Result from AccessTheWebAsync is stored in contentLength.\r\n" + " About to display contentLength and exit.\r\n"; resultsTextBox.Text += String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength); } async Task<int> AccessTheWebAsync() { resultsTextBox.Text += "\r\nTWO: Entering AccessTheWebAsync."; // Declare an HttpClient object. HttpClient client = new HttpClient(); resultsTextBox.Text += "\r\n Calling HttpClient.GetStringAsync.\r\n"; // GetStringAsync returns a Task<string>. Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com"); resultsTextBox.Text += "\r\nTHREE: Back in AccessTheWebAsync.\r\n" + " Task getStringTask is started."; // AccessTheWebAsync can continue to work until getStringTask is awaited. resultsTextBox.Text += "\r\n About to await getStringTask and return a Task<int> to startButton_Click.\r\n"; // Retrieve the website contents when task is complete. string urlContents = await getStringTask; resultsTextBox.Text += "\r\nFIVE: Back in AccessTheWebAsync." + "\r\n Task getStringTask is complete." + "\r\n Processing the return statement." + "\r\n Exiting from AccessTheWebAsync.\r\n"; return urlContents.Length; } } }
選取 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。 對於 AccessTheWebAsync,TResult 為整數。 對於 GetStringAsync,TResult 是字串。 如需非同步方法傳回類型的詳細資訊,請參閱非同步方法的傳回類型 (C# and Visual Basic)。
當控制權切換回呼叫端時,傳回工作的非同步方法會傳回工作執行個體。 當被呼叫方法遇到 Await 或 await 運算子,或者被呼叫方法結束時,控制權會從非同步方法返回呼叫端。 標示為「三」到「六」的顯示程式行會追蹤處理序的這個部分。
步驟三
在 AccessTheWebAsync 中,會呼叫 GetStringAsync(String) 非同步方法下載目標網頁的內容。 當 client.GetStringAsync 傳回時,控制權會從 client.GetStringAsync 返回 AccessTheWebAsync。
client.GetStringAsync 方法會傳回指派給 AccessTheWebAsync 中 getStringTask 變數之字串的工作。 範例程式中的下列程式行會顯示 client.GetStringAsync 的呼叫及指派。
Dim getStringTask As Task(Of String) = client.GetStringAsync("https://msdn.microsoft.com")
Task<string> getStringTask = client.GetStringAsync("https://msdn.microsoft.com");
您可以將工作視為 client.GetStringAsync 最後產生的實際字串。 同時,如果 AccessTheWebAsync 須執行的工作不是取決於 client.GetStringAsync 中承諾的字串,則該工作可以在 client.GetStringAsync 等候時繼續執行。 在範例中,下列幾行標記為 "THREE" 的輸出,表示可執行獨立工作的機會
THREE: Back in AccessTheWebAsync.
Task getStringTask is started.
About to await getStringTask & return a Task<int> to startButton_Click.
當 getStringTask 等候時,下列陳述式會暫停 AccessTheWebAsync 的進度。
Dim urlContents As String = Await getStringTask
string urlContents = await getStringTask;
下列影像顯示從 client.GetStringAsync 到指派至 getStringTask,以及從建立 getStringTask 到應用等候運算子的控制流程。
等候運算是會暫停 AccessTheWebAsync,直到 client.GetStringAsync 傳回。 同時,控制項會返回 AccessTheWebAsync 的呼叫端 startButton_Click。
注意事項 |
---|
通常,您會等候立即呼叫非同步方法。例如,下列其中一個指派可以取代先前建立然後等候 getStringTask 的程式碼:
在本主題中,稍後會套用等候運算子以容納指示控制流程傳遞程式的輸出行。 |
步驟四
AccessTheWebAsync 宣告的傳回類型是在 Visual Basic 是Task(Of Integer),在 C# 中是 Task<int>。 因此,當 AccessTheWebAsync 暫止時,它會傳回整數工作至 startButton_Click。 您應該了解傳回的工作並不是 getStringTask。 傳回的工作是整數的新工作,代表暫止的方法 AccessTheWebAsync 還需要完成的工作。 工作完成時,AccessTheWebAsync 一定會產生整數。
下列陳述式將這項工作指派給 getLengthTask 變數。
Dim getLengthTask As Task(Of Integer) = AccessTheWebAsync()
Task<int> getLengthTask = AccessTheWebAsync();
在 AccessTheWebAsync 中,startButton_Click 可以繼續執行不受非同步工作 (getLengthTask) 結果影響的工作,直到等候工作為止。 下列輸出行表示該工作。
FOUR: Back in startButton_Click.
Task getLengthTask is started.
About to await getLengthTask -- no caller to return to.
等候 getLengthTask 時,startButton_Click 中的進度會暫停。 下列指派陳述式會暫停 startButton_Click,直到 AccessTheWebAsync 完成為止。
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
在下圖中,箭號顯示從 AccessTheWebAsync 中的等候運算式到指派值給 getLengthTask 的控制流程,後面接著 startButton_Click 中的正常處理,直到等候 getLengthTask。
步驟五
當 client.GetStringAsync 發出完成訊號時,會從暫止狀態釋放 AccessTheWebAsync 中的處理,並且可以繼續通過等候陳述式。 下列輸出行表示復原處理。
FIVE: Back in AccessTheWebAsync.
Task getStringTask is complete.
Processing the return statement.
Exiting from AccessTheWebAsync.
回傳陳述式的運算元 urlContents.Length 存在 AccessTheWebAsync 傳回的工作中。 等候運算式會從 startButton_Click 中的 getLengthTask 擷取該值。
下列影像顯示 client.GetStringAsync (和 getStringTask) 之後的傳輸控制完成。
AccessTheWebAsync 會執行至完成,並且控制權會返回正在等候完成的 startButton_Click。
步驟六
當 AccessTheWebAsync 發出完成訊號時,處理序可以繼續通過在 startButton_Async等候的陳述式。 實際上,程式不需再執行其他動作。
下列輸出行表示在 startButton_Async 中復原處理:
SIX: Back in startButton_Click.
Task getLengthTask is finished.
Result from AccessTheWebAsync is stored in contentLength.
About to display contentLength and exit.
等候運算式從 getLengthTask 擷取整數值,該值是 AccessTheWebAsync 中 return 陳述式的運算元。 下列陳述式將該值指派給 contentLength 變數。
Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;
下列影像顯示從 AccessTheWebAsync 將控制項傳回到 startButton_Click。
請參閱
工作
逐步解說:使用 Async 和 Await 存取 Web (C# 和 Visual Basic)
概念
使用 Async 和 Await 設計非同步程式 (C# 和 Visual Basic)
非同步方法的傳回類型 (C# and Visual Basic)