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

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

注意

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

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

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

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

  • Web サイトのコンテンツを文字列としてダウンロードして、その文字列の長さを返す AccessTheWebAsyncAccessTheWebAsync は、非同期 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

「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 以降と .NET Framework 4.5 以降が、コンピューターにインストールされている必要があります。

プログラムをダウンロードする

このトピックのアプリケーションは、「非同期のサンプル:非同期プログラムにおける制御フロー」からダウンロードできます。 次の手順でプログラムを開いて実行します。

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

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

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

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

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

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

  1. Visual Studio を起動します。

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

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

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

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

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

  5. Visual Studio コード エディターで、 [MainWindow.xaml] タブをクリックします。

    タブが表示されない場合は、ソリューション エクスプローラーで MainWindow.xaml のショートカット メニューを開き、 [コードの表示] を選択します。

  6. 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 のデザイン ビューに表示されます。

  7. System.Net.Http への参照を追加します。

  8. ソリューション エクスプローラーで MainWindow.xaml.vb のショートカット メニューを開き、 [コードの表示] を選択します。

  9. 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
    
  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_ClickAccessTheWebAsync を呼び出し、AccessTheWebAsync が非同期 HttpClient メソッド GetStringAsync(String) を呼び出すと、最初の 2 行の表示行がパスをトレースします。 次の図は、メソッドからメソッドへの呼び出しを示しています。

Steps ONE and TWO

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

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

手順 3.

AccessTheWebAsync で非同期メソッド GetStringAsync(String) が呼び出され、ターゲットの Web ページのコンテンツがダウンロードされます。 client.GetStringAsync が制御を返すと、AccessTheWebAsync から client.GetStringAsync に制御が戻ります。

client.GetStringAsync メソッドは、getStringTaskAccessTheWebAsync 変数に割り当てる文字列のタスクを返します。 プログラム例の次の行は、client.GetStringAsync の呼び出しと割り当てを示しています。

Dim getStringTask As Task(Of String) = client.GetStringAsync("https://learn.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.

次のステートメントは AccessTheWebAsync が待機中の場合 getStringTask の進行を中断します。

Dim urlContents As String = Await getStringTask

次の図は client.GetStringAsync から getStringTask への割り当てへの制御フロー、および getStringTask の作成から Await 演算子のアプリケーションへの制御フローを示しています。

Step THREE

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

注意

通常、直ちに非同期メソッドへの呼び出しの待機状態となります。 たとえば、次の割り当てで、getStringTask を作成してそれを待機する前のコードを置き換えることができます: Dim urlContents As String = Await client.GetStringAsync("https://learn.microsoft.com")

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

手順 4.

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

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

Step FOUR

手順 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 式はその値を getLengthTaskstartButton_Click から取得します。

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

Step FIVE

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

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

Step SIX

関連項目