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

パフォーマンスのボトルネックを回避しアプリケーション全体の応答性を向上させるために、非同期プログラミングを使用できます。 ただ、非同期アプリケーションを作成する従来の方法は複雑で、プログラムの作成、デバッグ、保守が困難な場合があります。

Visual Studio 2012 では、.NET Framework 4.5 以降と Windows ランタイムの非同期サポートを利用した "非同期プログラミング" と呼ばれる簡単な方法が導入されました。 コンパイラがこれまで開発者が行っていた難しい作業を実行し、アプリケーションは同期コードに類似した論理構造を保持します。 その結果、わずかな作業量で非同期プログラミングのすべての利点を得られます。

このトピックでは、非同期プログラミングをいつ、どのように使用するかの概要を紹介します。詳細と例を含むをサポート トピックへのリンクもあります。

非同期による応答性の改善

アプリケーションが Web サイトにアクセスする場合など、ブロックする可能性がある操作には、非同期が必要となります。 Web リソースへのアクセスには、遅延が発生することがあります。 このような操作が同期処理内でブロックされた場合、アプリケーション全体が待機する必要があります。 非同期処理では、ブロックする可能性のあるタスク終了するまで、アプリケーションは Web リソースに依存しない他の操作を続行できます。

非同期プログラミングによって応答性を向上する一般的な領域を、次の表に示します。 .NET Framework 4.5 および Windows ランタイムの API の一覧には、非同期のプログラミングをサポートするメソッドが含まれます。

アプリケーション領域 サポートされている、非同期のメソッドを含む API
Web アクセス HttpClient, SyndicationClient
ファイルの処理 StorageFile, StreamWriter, StreamReader, XmlReader
イメージの処理 MediaCapture, BitmapEncoder, BitmapDecoder
WCF プログラミング 同期操作と非同期操作

非同期性は、UI スレッドにアクセスするアプリケーションに対して特に有効です。これは、すべての UI 関連のアクティビティが一般的に 1 つのスレッドを共有するためです。 同期アプリケーションでは、1 つのプロセスがブロックされるとすべてがブロックされます。 アプリケーションが応答を停止するため、待機状態であるとは考えずに失敗したと結論付けることもあります。

非同期メソッドを使用すると、アプリケーションは UI に応答し続けます。 たとえば、ウィンドウのサイズ変更や最小化を実行したり、アプリケーション処理の完了待たずに、アプリケーションを閉じたりできます。

非同期ベースの方法は、非同期操作を設計する場合に選択できるオプションの一覧に、自動送信に相当するものを追加します。 つまり、開発者の少しの作業量で、従来の非同期プログラミングのすべての利点を取得できます。

非同期メソッドの作成の簡素化

Visual Basic の Async キーワードと Await キーワードは、非同期プログラミングの中核です。 これら 2 つのキーワードを使用すると、同期メソッドの作成とほぼ同様の容易さで、.NET Framework または Windows ランタイムのリソースを使用して非同期メソッドを作成できます。 Async および Await を使用して定義する非同期メソッドは、async メソッドとして参照されます。

async メソッドの例を次に示します。 コードのほとんどは、見たことのあるものと思います。 コメントは、非同期性を作成するために追加した機能を明示しています。

このトピックの最後に完全な Windows Presentation Foundation (WPF) サンプル ファイルがあります。また、「Async Sample: Example from "Asynchronous Programming with Async and Await" (非同期のサンプル: 「Async および Await を使用した非同期プログラミング」の例)」からサンプルをダウンロードできます。

' Three things to note about writing an Async Function:
'  - The function has an Async modifier.
'  - Its return type is Task or Task(Of T). (See "Return Types" section.)
'  - As a matter of convention, its name ends in "Async".
Async Function AccessTheWebAsync() As Task(Of Integer)
    Using client As New HttpClient()
        ' Call and await separately.
        '  - AccessTheWebAsync can do other things while GetStringAsync is also running.
        '  - getStringTask stores the task we get from the call to GetStringAsync.
        '  - Task(Of String) means it is a task which returns a String when it is done.
        Dim getStringTask As Task(Of String) =
            client.GetStringAsync("https://docs.microsoft.com/dotnet")
        ' You can do other work here that doesn't rely on the string from GetStringAsync.
        DoIndependentWork()
        ' The Await operator suspends AccessTheWebAsync.
        '  - AccessTheWebAsync does not continue until getStringTask is complete.
        '  - Meanwhile, control returns to the caller of AccessTheWebAsync.
        '  - Control resumes here when getStringTask is complete.
        '  - The Await operator then retrieves the String result from getStringTask.
        Dim urlContents As String = Await getStringTask
        ' The Return statement specifies an Integer result.
        ' A method which awaits AccessTheWebAsync receives the Length value.
        Return urlContents.Length

    End Using

End Function

AccessTheWebAsyncGetStringAsync を呼び出してその完了を待機する間で実行できる作業がない場合、次の 1 つのステートメントで呼び出しと待機をするようにコードを簡略化できます。

Dim urlContents As String = Await client.GetStringAsync()

次の特徴は、前の例を非同期のメソッドにするための概略です。

  • メソッド シグネチャは Async 修飾子を含みます。

  • 非同期メソッドの名前は、慣例により「Async」というサフィックスで終わります。

  • 戻り値の型は次のいずれかになります:

    • メソッドが、オペランドに TResult 型を持つステートメントを戻す場合、Task(Of TResult)
    • メソッドがステートメントを戻さない、またはオペランドを持たないステートメントを戻す場合、Task
    • 非同期のイベント ハンドラーを作成する場合、Sub

    詳細については、このトピックで後述する「戻り値の型およびパラメーター」を参照してください。

  • メソッドには、通常は 1 つ以上の await 式があり、待機中の非同期操作が完了するまでメソッドを続行できないポイントをマークします。 この間メソッドは中断し、メソッドの呼び出し元にコントロールを戻します。 このトピックの次のセクションでは、中断ポイントで何が発生するかを説明します。

非同期のメソッドでは、指定のキーワードと型を使用して何を実行するかを示すと、コンパイラがその作業を引き継ぎます。作業には、中断されたメソッドの待機ポイントにコントロールが戻された場合に実行される作業を、継続的に追跡することも含まれます。 ループおよび例外処理など一部のルーチンのプロセスは、従来の非同期コードによる操作が困難な場合があります。 非同期のメソッドでは、同期ソリューションの場合と同様にこれらの要素を記述すると、問題が解決します。

.NET Framework の以前のバージョンでの非同期性の詳細については、「TPL と従来の .NET Framework 非同期プログラミング」を参照してください。

非同期メソッドでの動作

非同期プログラミングでは理解が必要な最も重要なことは、コントロール フローがどのようにメソッドからのメソッドに移動するかということです。 次の図では、このプロセスについて説明します。

Diagram that shows tracing an async program.

図内の数字は、次の手順の番号に対応しています。

  1. イベント ハンドラーは AccessTheWebAsync 非同期のメソッドを呼び出して待機します。

  2. AccessTheWebAsyncHttpClient インスタンスを作成し、文字列として Web サイトのコンテンツをダウンロードする GetStringAsync 非同期メソッドを呼び出します。

  3. GetStringAsync に何かが発生するとプロセスが中断します。 Web サイトからのダウンロード処理、または他のブロックしているアクティビティを待機する必要が考えられます。 リソースのブロックを回避するために、GetStringAsync は呼び出し元の AccessTheWebAsync にコントロールを戻します。

    GetStringAsync は TResult が文字列である Task(Of TResult) を返し、AccessTheWebAsyncgetStringTask 変数にタスクを割り当てます。 タスクには GetStringAsync への呼び出しの進行中のプロセスを表し、作業が完了すると実際の文字列値を生成するコミットメントがあります。

  4. getStringTask が待機しないため、AccessTheWebAsyncGetStringAsync からの最終結果に依存しない他の作業を続行できます。 この作業は同期メソッド DoIndependentWork への呼び出しによって表されます。

  5. DoIndependentWork は、作業を実行し、呼び出し元に戻る同期メソッドです。

  6. AccessTheWebAsyncgetStringTask からの結果なしで実行できる作業を使い果たしました。 AccessTheWebAsync は次に、ダウンロードする文字列の長さを計算しますが、メソッドに文字列が戻されるまで、メソッドはその値を計算できません。

    そのため、AccessTheWebAsync は await 演算子を使用してその進行を中断し、AccessTheWebAsync を呼び出したメソッドにコントロールを戻します。 AccessTheWebAsync は呼び出し元に Task(Of Integer) を返します。 タスクは、ダウンロードされた文字列の長さの整数値を生成することの保証を表します。

    Note

    GetStringAsync (および、結果として getStringTask) が AccessTheWebAsync が待機する前に完了した場合、コントロールは AccessTheWebAsync に残ります。 AccessTheWebAsync を中断して後から戻ることは、呼び出された非同期プロセス (getStringTask) が既に完了していて、AccessTheWebSync が最終結果を待つ必要がない場合に、無駄になることがあります。

    呼び出し元 (この例ではイベント ハンドラー) の内部で、処理パターンが続行されます。 呼び出し元は AccessTheWebAsync からの結果に依存しない他の作業をすることもあり、または直ちに待機状態になることもあります。 イベント ハンドラーは AccessTheWebAsync を待機し、AccessTheWebAsync は、GetStringAsync を待機します。

  7. GetStringAsync が完了し、文字列の結果を生成します。 文字列の結果は、GetStringAsync への呼び出しによって、意図した形式では戻されません。 (メソッドは既に手順 3 のタスクで戻されていることに注意してください)。代わりに、文字列の結果は、getStringTask メソッドの完了を表すタスク内に格納されます。 await 演算子は、getStringTask から結果を取得します。 代入ステートメントは urlContents に取得された結果を割り当てます。

  8. AccessTheWebAsync に文字列の結果がある場合、メソッドは文字列の長さを計算できます。 次に AccessTheWebAsync の作業も完了し、待機しているイベント ハンドラーが再開できます。 トピックの最後にある完全なサンプルでは、イベント ハンドラーが長さの結果の値を取得して印刷することを確認できます。

非同期プログラミングの経験がない場合、同期および非同期の動作の違いを、少し時間を割いて考慮してください。 同期メソッドは作業が完了すると戻されます (手順 5.) が、非同期のメソッドは、作業が中断されるとタスクの値を戻します。(手順 3. および 6.) 非同期のメソッドが最終的に作業を完了すると、タスクは完了とマークされ、結果が存在する場合はタスクに格納されます。

制御フローの詳細については、「非同期プログラムにおける制御フロー (Visual Basic)」を参照してください。

API の非同期メソッド

非同期のプログラミングをサポートする GetStringAsync などのメソッドがどこにあるのかということです。 .NET Framework 4.5 以降には、Async および Await で使用する多くのメンバーが含まれています。 メンバー名と Task または Task(Of TResult) の戻り値の型に付けられている "Async" というサフィックスでこれらのメンバーを識別できます。 たとえば、System.IO.Stream のクラスには、同期メソッドの CopyToRead、および Write と共に、CopyToAsyncReadAsync および WriteAsync という同期メソッドが含まれています。

Windows ランタイムにも、Windows アプリの AsyncAwait で使用できる多くのメソッドが含まれています。 詳細とメソッドの例については、「C# または Visual Basic での非同期 API の呼び出し」、「非同期プログラミング (Windows ランタイム アプリ)」、および「WhenAny: .NET Framework と Windows ランタイム間のブリッジ」を参照してください。

Threads

非同期のメソッドは非ブロッキング操作を意図しています。 非同期のメソッドの Await 式では、待機中のタスクの実行時に現在のスレッドはブロックされません。 代わりに、式はメソッドの残りの部分の継続を登録し、非同期のメソッドの呼び出し元にコントロールを戻します。

Async および Await キーワードは、追加のスレッドを作成する要因にはなりません。 非同期のメソッドは自分自身のスレッドで実行しないため、マルチスレッドは必要ありません。 メソッドは、現在の同期コンテキストで実行し、メソッドがアクティブな場合に限りスレッドの時間を使用します。 Task.Run を使用して、CPU バインディングの作業をバックグラウンド スレッドに移動できますが、バックグラウンド スレッドは、結果を待つだけのプロセスを援助しません。

非同期プログラミングへの非同期ベースのアプローチは、ほぼすべてのケースの既存のアプローチに推奨されます。 特に、このアプローチはコードがシンプルで競合状態からの保護の必要がないため、I/O バインディングの操作では、BackgroundWorker よりも優れています。 Task.Run と組み合わせると、非同期のプログラミングは CPU バインディングの操作に関して BackgroundWorker よりも優れています。非同期のプログラミングは、Task.Run がスレッド プールから移動する作業から、実行するコードの調整の詳細を分離するためです。

Async と Await

Async 修飾子を使用して、メソッドが非同期メソッドであることを指定すると、次の 2 つの機能が有効になります。

  • マークされた非同期のメソッドは中断ポイントを示すために Await を使用できます。 await 演算子は、非同期のメソッドが、待機中の非同期のプロセスが完了するまでこのポイント以降を続行できないことを、コンパイラに指示します。 その間、コントロールは非同期のメソッドの呼び出し元に戻されます。

    非同期のメソッドの Await 式での中断は、メソッドからの終了を意図するものではなく、Finally ブロックは実行されません。

  • マークされた非同期のメソッド自体は、呼び出し元のメソッドによって待機できます。

非同期のメソッドには、通常の Await 演算子が 1 つ以上ありますが、Await 式がない場合もコンパイラ エラーの原因にはなりません。 中断ポイントをマークするために非同期のメソッドが Await 演算子を使用しない場合、Async 修飾子が存在しても、メソッドは同期メソッドと同様に実行されます。 このようなメソッドには、コンパイラが警告を発行します。

AsyncAwait は、コンテキスト キーワードです。 詳細およびサンプルについては、次のトピックを参照してください:

戻り値の型およびパラメーター

.NET Framework プログラミングでは、非同期のメソッドは、一般的に Task、または Task(Of TResult) を戻します。 非同期のメソッド内で、Await 演算子は、他の非同期のメソッドへの呼び出しから戻されたタスクに適用されます。

メソッドが、TResult 型のオペランドを指定する Return ステートメントを含む場合、Task(Of TResult) を戻り値の型として指定します。

メソッドに Return ステートメントがない場合、または Return ステートメントがオペランドを戻さない場合、Task を戻り値の型として使用します。

次のサンプルは、Task(Of TResult) または Task を戻すメソッドを宣言して呼び出す方法を示します。

' Signature specifies Task(Of Integer)
Async Function TaskOfTResult_MethodAsync() As Task(Of Integer)

    Dim hours As Integer
    ' . . .
    ' Return statement specifies an integer result.
    Return hours
End Function

' Calls to TaskOfTResult_MethodAsync
Dim returnedTaskTResult As Task(Of Integer) = TaskOfTResult_MethodAsync()
Dim intResult As Integer = Await returnedTaskTResult
' or, in a single statement
Dim intResult As Integer = Await TaskOfTResult_MethodAsync()

' Signature specifies Task
Async Function Task_MethodAsync() As Task

    ' . . .
    ' The method has no return statement.
End Function

' Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync()
Await returnedTask
' or, in a single statement
Await Task_MethodAsync()

それぞれ、進行中の作業を示すタスクを戻します。 タスクに非同期処理の状態に関する情報、および最終的にはプロセスからの最終結果、またはプロセスが成功しなかった場合に発生する例外をカプセル化します。

非同期のメソッドは、Sub メソッドにもなります。 この戻り値の型は主として、戻り値の型が必要なイベント ハンドラーの定義に使用されます。 非同期のイベント ハンドラーは通常、非同期のプログラムの開始点として機能します。

Sub プロシージャである非同期のメソッドは、待機できません。呼び出し元は、このメソッドがスローする例外をキャッチできません。

非同期のメソッドで ByRef パラメーターを宣言することはできませんが、これらのパラメーターを持つメソッドを呼び出すことはできます。

使用例を含む詳細については、「非同期の戻り値の型 (Visual Basic)」を参照してください。 非同期のメソッドで例外をキャッチする方法の詳細については、「Try...Catch...Finally Statement (Try...Catch...Finally ステートメント)」を参照してください。

Windows ランタイム プログラミングの非同期 API には、タスクに類似した次のような戻り値の型の 1 つがあります。

詳細と例については、「C# または Visual Basic での非同期 API の呼び出し」を参照してください。

命名規則

慣例により、Async 修飾子を持つメソッドの名前には、"Async" を追加します。

イベント、基底クラス、またはインターフェイスのコントラクトが別の名前を表示している場合は、この慣例を無視できます。 たとえば、Button1_Click などの共通のイベント ハンドラーの名前は、変更しないことをお勧めします。

関連トピックとサンプル (Visual Studio)

タイトル 説明 サンプル
チュートリアル: Async と Await を使用した Web へのアクセス (Visual Basic) 同期 WPF のソリューションを非同期 WPF のソリューションに変換する方法を示します。 アプリケーションは、一連の Web サイトをダウンロードします。 Async のサンプル: Async および Await を使用した非同期プログラミング (Visual Basic)
方法: Task.WhenAll を使用して非同期のチュートリアルを拡張する (Visual Basic) 前のチュートリアルに Task.WhenAll を追加します。 WhenAll を使用すると、すべてのダウンロードが同時に開始します。
方法: Async と Await を使用して複数の Web 要求を並列実行する (Visual Basic) 複数のタスクを同時に開始する方法を示します。 Async Sample:Make Multiple Web Requests in Parallel (非同期のサンプル: 複数の Web 要求を並行して作成する)
非同期の戻り値の型 (Visual Basic) 非同期のメソッドが戻す型、および各型の適切な使用方法を説明します。
非同期プログラムにおける制御フロー (Visual Basic) 非同期プログラムでの await 式を継続して、コントロールのフローの詳細をトレースします。 非同期のサンプル:非同期プログラムにおける制御フロー
非同期アプリケーションの微調整 (Visual Basic) 非同期のソリューションに次の機能を追加する方法を示します:

- 非同期タスクまたはタスクの一覧のキャンセル (Visual Basic)
- 指定した時間の経過後の非同期タスクのキャンセル (Visual Basic)
- 完了後の残りの非同期タスクのキャンセル (Visual Basic)
- 完了時での複数の同期タスクとプロセスの実行 (Visual Basic)
Async Sample:Fine Tuning Your Application (非同期のサンプル: アプリケーションの微調整)
非同期アプリにおける再入の処理 (Visual Basic) 実行中にアクティブな非同期操作を再起動するケースの処理方法を示します。
WhenAny:.NET Framework と Windows ランタイム間のブリッジ Windows ランタイムのメソッドで WhenAny を使えるようにするため、Windows ランタイムで .NET Framework および IAsyncOperations のタスクの種類間をブリッジする方法を示します。 Async Sample:Bridging between .NET and Windows Runtime (AsTask and WhenAny) (非同期のサンプル: .NET と Windows ランタイム間のブリッジ (AsTask と WhenAny))
非同期のキャンセル:.NET Framework と Windows ランタイム間のブリッジ Windows ランタイムのメソッドで CancellationTokenSource を使えるようにするため、Windows ランタイムで .NET Framework および IAsyncOperations のタスクの種類間をブリッジする方法を示します。 非同期のサンプル: .NET と Windows ランタイム間のブリッジ (AsTask と Cancellation)
ファイル アクセスにおける非同期の使用 (Visual Basic) async および await を使用してファイルにアクセスすることの利点の一覧と紹介です。
タスク ベースの非同期パターン (TAP) .NET Framework での非同期性の新しいパターンについて説明します。 パターンは Task および Task(Of TResult) の型に基づいています。
Channel 9 の非同期に関するビデオ 非同期のプログラミングに関するさまざまなビデオへのリンクを示します。

コード例全体

次のコードは、このトピックで説明する Windows Presentation Foundation (WPF) アプリケーションの MainWindow.xaml.vb ファイルです。 「Async Sample:Example from "Asynchronous Programming with Async and Await" (非同期のサンプル: 「Async および Await を使用した非同期プログラミング」の例)」からサンプルをダウンロードできます。


Imports System.Net.Http

' Example that demonstrates Asynchronous Progamming with Async and Await.
' It uses HttpClient.GetStringAsync to download the contents of a website.
' Sample Output:
' Working . . . . . . .
'
' Length of the downloaded string: 39678.

Class MainWindow

    ' Mark the event handler with Async so you can use Await in it.
    Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)

        ' Call and await immediately.
        ' StartButton_Click suspends until AccessTheWebAsync is done.
        Dim contentLength As Integer = Await AccessTheWebAsync()

        ResultsTextBox.Text &= $"{vbCrLf}Length of the downloaded string: {contentLength}.{vbCrLf}"

    End Sub


    ' Three things to note about writing an Async Function:
    '  - The function has an Async modifier. 
    '  - Its return type is Task or Task(Of T). (See "Return Types" section.)
    '  - As a matter of convention, its name ends in "Async".
    Async Function AccessTheWebAsync() As Task(Of Integer)

        Using client As New HttpClient()

            ' Call and await separately. 
            '  - AccessTheWebAsync can do other things while GetStringAsync is also running.
            '  - getStringTask stores the task we get from the call to GetStringAsync. 
            '  - Task(Of String) means it is a task which returns a String when it is done.
            Dim getStringTask As Task(Of String) =
                client.GetStringAsync("https://docs.microsoft.com/dotnet")

            ' You can do other work here that doesn't rely on the string from GetStringAsync. 
            DoIndependentWork()

            ' The Await operator suspends AccessTheWebAsync.
            '  - AccessTheWebAsync does not continue until getStringTask is complete.
            '  - Meanwhile, control returns to the caller of AccessTheWebAsync.
            '  - Control resumes here when getStringTask is complete.
            '  - The Await operator then retrieves the String result from getStringTask.
            Dim urlContents As String = Await getStringTask

            ' The Return statement specifies an Integer result.
            ' A method which awaits AccessTheWebAsync receives the Length value.
            Return urlContents.Length

        End Using

    End Function

    Sub DoIndependentWork()
        ResultsTextBox.Text &= $"Working . . . . . . .{vbCrLf}"
    End Sub

End Class

関連項目