共用方式為


非同步程式中的控制流程 (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 範例:在非同步程式的控制流程 下載本主題的應用程式。 下列步驟會開啟並執行程式。

  1. 解壓縮下載的檔案,然後啟動 Visual Studio。

  2. 在功能表列上,選擇 [檔案]、[開啟]、[專案/方案]。

  3. 導覽至存放解壓縮的範例程式碼的資料夾,開啟方案檔 (.sln),然後選擇 F5 鍵建置及執行專案。

自行建立程式

下列 Windows Presentation Foundation (WPF) 專案包含本主題中的程式碼範例。

若要執行專案,請執行下列步驟:

  1. 啟動 Visual Studio。

  2. 在功能表列上,選擇 [檔案]、[新增]、[專案]。

    [新增專案] 對話方塊隨即開啟。

  3. 在 [安裝的範本] 窗格中,選擇 [Visual Basic] 或 [Visual C#],從專案類型清單中選擇 [WPF 應用程式]。

  4. 輸入 AsyncTracer 做為專案名稱,然後選擇 [確定] 按鈕。

    新專案即會出現於 [方案總管] 中。

  5. 在 Visual Studio 程式碼編輯器中,選擇 [MainWindow.xaml] 索引標籤。

    如果未顯示索引標籤,請在 [方案總管] 中開啟 MainWindow.xaml 的捷徑功能表,然後選擇 [檢視程式碼]。

  6. 在 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&#xa;" 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 的 [設計] 檢視中。

  7. 加入 System.Net.Http 的參考。

  8. 在 [方案總管] 中,開啟 MainWindow.xaml.vb 或 MainWindow.xaml.cs 的捷徑功能表,然後選擇 [檢視程式碼]。

  9. 在 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;
            }
        }
    }
    
  10. 選取 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 的程式碼:

  • Visual Basic:Dim urlContents As String = Await client.GetStringAsync("http://msdn.microsoft.com")

  • C#:string urlContents = await client.GetStringAsync("http://msdn.microsoft.com");

在本主題中,稍後會套用等候運算子以容納指示控制流程傳遞程式的輸出行。

步驟四

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)

其他資源

非同步範例:控制非同步程式 (C# 和 Visual Basic) 中的流程