非同步程式中的控制流程 (C# 和 Visual Basic)
您可以使用 Async 和 Await 關鍵字,可以更輕鬆地撰寫和維護非同步程式。不過,結果可能會讓您驚訝,如果您不知道您的程式運作的方式。這個主題追蹤控制流程透過簡單的非同步程式來顯示給您看,控制項從一個移到另一個,而且每次資訊將如何傳遞。
注意事項 |
---|
Async 與 Await 關鍵字在 Visual Studio 2012 中引入。如需在該版本的新功能的詳細資訊,請參閱 Visual Studio 2012 的新功能。 |
一般而言,您可以標記包含具有 Async (Visual Basic) 或 非同步 (C#) 修飾詞的非同步程式碼的方法。在標記為非同步修飾詞的方法,您可以使用 等候 (Visual Basic) 或 等候 (C#) 運算子來指定方法的位置暫停等候呼叫的非同步處理序完成。如需詳細資訊,請參閱使用 Async 和 Await 設計非同步程式 (C# 和 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 &=
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 Express 2012 for 或在您的電腦上安裝 .NET Framework 4.5。 |
下載程式
您可以從 用於範例:在用於程式的控制流程下載此主題的應用程式。下列步驟開啟並執行程式。
解壓縮下載的檔案,然後啟動 Visual Studio 2012。
在功能表列上,選擇 [檔案]、[開啟]、[專案/方案]。
巡覽至存放解壓縮的範例程式碼的資料夾,然後開啟方案檔 (.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 呼叫非同步方法 HttpClientGetStringAsync(String)。下圖說明從方法呼叫方法。
兩個 AccessTheWebAsync 和 client.GetStringAsync 的傳回型別為 Task<TResult>。對於, AccessTheWebAsyncTResult 是整數。對於, GetStringAsyncTResult 是字串。如需非同步方法傳回型別的詳細資訊,請參閱 非同步方法的傳回型別 (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 和 C# 中的 Task<int>Task(Of Integer) 。因此,當 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_ClickAccessTheWebAsync ,直到完成為止。
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 擷取是 return 陳述式運算元在 AccessTheWebAsync的整數值。下列陳述式將該值指派給 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)