非同期プログラムにおける制御フロー (C# および Visual Basic)

Async キーワードと Await キーワードを使用すると、非同期のプログラムの作成と保守をより簡単に行えます。 ただし、プログラムがどのように動作するかを理解しないと、その結果は予想に反するものになる場合があります。 このトピックでは、簡単な非同期プログラムによる制御フローをトレースして、制御があるメソッドから別のメソッドに移るタイミングと、その都度転送される情報について説明します。

注意

Async キーワードおよび Await キーワードは、Visual Studio 2012 で導入されました。

一般に、Async (Visual Basic) または async (C#) 修飾子を使用した非同期コードを含むメソッドをマークします。 async 修飾子でマークされたメソッドでは、Await (Visual Basic) または await (C#) 演算子を使用して、呼び出される非同期処理の終了をメソッドが待機する場所を指定できます。 詳細については、「Async および Await を使用した非同期プログラミング (C# および Visual Basic)」を参照してください。

次の例では、非同期メソッドを使用して、指定した Web サイトのコンテンツを文字列としてダウンロードし、その文字列の長さを表示します。 この例には、次の 2 つのメソッドが含まれています。

  • AccessTheWebAsync を呼び出して結果を表示する startButton_Click。

  • Web サイトのコンテンツを文字列としてダウンロードして、その文字列の長さを返す AccessTheWebAsync。 AccessTheWebAsync は、非同期 HttpClient メソッドである GetStringAsync(String) を使用してコンテンツをダウンロードします。

番号付き表示行はプログラム全体で重要なポイントを示し、プログラムがどのように実行され、マークされている各ポイントで何が発生するかを理解するために役立ちます。 表示行には「1」から「6」までのラベルが付けられています。このラベルは、プログラムがこれらのコード行に到達する順序を表します。

次のコードは、プログラムの概要を示します。

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;
    }
}

「1」から「6」までのそれぞれのラベルの位置は、プログラムの現在の状態に関する情報を表示します。 次の出力が生成されます。

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 Sample: Control Flow in Async Programs (非同期のサンプル: 非同期のプログラムのコントロール フロー) からダウンロードできます。 次の手順でプログラムを開いて実行します。

  1. ダウンロードしたファイルを解凍し、Visual Studio を開始します。

  2. メニュー バーで [ファイル][開く][プロジェクト/ソリューション] の順に選択します。

  3. 解凍したサンプル コードが含まれるフォルダーに移動し、ソリューション (.sln) ファイルを開き、F5 キーを押してプロジェクトをビルドし、実行します。

プログラムを手動でビルドする

次の Windows Presentation Foundation (WPF) プロジェクトには、このトピックのコード例が含まれています。

このプロジェクトを実行するには、次の手順を実行します。

  1. Visual Studio を起動します。

  2. メニュー バーで [ファイル][新規][プロジェクト] の順にクリックします。

    [新しいプロジェクト] ダイアログ ボックスが表示されます。

  3. [インストールされたテンプレート] ペインで、[Visual Basic] または [Visual C#] をクリックし、プロジェクトの種類の一覧の [WPF アプリケーション] をクリックします。

  4. プロジェクトの名前として「AsyncTracer」と入力し、[OK] をクリックします。

    ソリューション エクスプローラーに新しいプロジェクトが表示されます。

  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 キーを押してプログラムを実行し、[Start] を複数回クリックします。

    次の出力が表示されます。

    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.
    

プログラムのトレース

手順 1 と 2

startButton_Click が AccessTheWebAsync を呼び出し、AccessTheWebAsync が非同期 HttpClient メソッド GetStringAsync(String) を呼び出すと、最初の 2 行の表示行がパスをトレースします。 次の図は、メソッドからメソッドへの呼び出しを示しています。

手順 1. および 2.

AccessTheWebAsync と client.GetStringAsync の戻り値の型はどちらも Task です。 AccessTheWebAsync では、TResult は整数です。 GetStringAsync では、TResult は文字列です。 非同期メソッドの戻り値の型の詳細については、「非同期の戻り値の型 (C# および Visual Basic)」を参照してください。

タスクを返す非同期のメソッドは、制御が呼び出し元に戻ると、タスク インスタンスを返します。 Await または await 演算子が呼び出されたメソッドで実行されるか、または呼び出されたメソッドが終了すると、非同期メソッドから呼び出し元に制御が戻ります。 「3」から「6」のラベルの付いた表示行はこのプロセスの部分をトレースします。

手順 3

AccessTheWebAsync で非同期メソッド GetStringAsync(String) が呼び出され、ターゲットの Web ページのコンテンツがダウンロードされます。 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 を待機している間は、続行できます。 この例では、「3」のラベルの付いた行の出力は、独立した処理を行う機会を表します。

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 の作成から await 演算子のアプリケーションへの制御フローを示しています。

手順 3.

await 式は client.GetStringAsync が制御を返すまで AccessTheWebAsync を中断します。 その間、コントロールは AccessTheWebAsync の呼び出し元である startButton_Click に戻されます。

注意

通常、直ちに非同期メソッドへの呼び出しの待機状態となります。たとえば、次の割り当ての 1 つが、getStringTask を作成してそれを待機する、前のコードを置換する場合があります。

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

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

このトピックでは、await 演算子が後で適用され、プログラムでの制御フローを示す出力行を格納します。

手順 4

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 の進行は中断します。 次の代入ステートメントは、AccessTheWebAsync が完了するまで startButton_Click を中断します。

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

次の図で、矢印は AccessTheWebAsync の await 式から getLengthTask への値の割り当てへの制御のフロー、および getLengthTask が待機するまでの startButton_Click の通常の処理を示しています。

手順 4.

手順 5

client.GetStringAsync が終了を通知すると、AccessTheWebAsync の処理は中断から解放され、await ステートメントを越えて続行できます。 次の出力行は、処理の再開を表します。

FIVE:  Back in AccessTheWebAsync.
           Task getStringTask is complete.
           Processing the return statement.
           Exiting from AccessTheWebAsync.

return ステートメントのオペランド urlContents.Length は AccessTheWebAsync が返すタスクに格納されます。 await 式はその値を startButton_Click の getLengthTask から取得します。

次の図は、client.GetStringAsync (および getStringTask) が完了した後の制御の移動を示します。

手順 5.

AccessTheWebAsync は完了するまで実行され、完了を待機していた startButton_Click に制御が戻ります。

手順 6

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 の return ステートメントのオペランドである整数値を取得します。 次のステートメントはその値を contentLength 変数に割り当てます。

Dim contentLength As Integer = Await getLengthTask
int contentLength = await getLengthTask;

次の図は AccessTheWebAsync から startButton_Click に制御が戻ることを示しています。

手順 6.

参照

処理手順

チュートリアル: Async と Await を使用した Web へのアクセス (C# および Visual Basic)

チュートリアル: 非同期メソッドと共にデバッガーを使用する

概念

Async および Await を使用した非同期プログラミング (C# および Visual Basic)

非同期の戻り値の型 (C# および Visual Basic)

その他の技術情報

Async Sample: Control Flow in Async Programs (C# and Visual Basic) (非同期のサンプル: 非同期のプログラムのコントロール フロー (C# および Visual Basic))