非同期プログラミングを使用すると、パフォーマンスのボトルネックを回避し、アプリケーションの全体的な応答性を向上させることができます。 ただし、非同期アプリケーションを記述するための従来の手法は複雑になり、書き込み、デバッグ、保守が困難になる場合があります。
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 関連アクティビティが通常 1 つのスレッドを共有するため、非同期性は UI スレッドにアクセスするアプリケーションにとって特に重要です。 同期アプリケーションでプロセスがブロックされると、すべてがブロックされます。 アプリケーションが応答を停止し、実際にはただ待機しているだけなのに、故障したと判断してしまうことがあります。
非同期メソッドを使用すると、アプリケーションは引き続き UI に応答します。 たとえば、ウィンドウのサイズを変更したり最小化したり、アプリケーションが終了するのを待たないようにする場合は、アプリケーションを閉じることができます。
非同期ベースのアプローチでは、非同期操作を設計するときに選択できるオプションの一覧に、自動転送と同等の機能が追加されます。 つまり、従来の非同期プログラミングのすべての利点を得ることができますが、開発者の労力ははるかに少なくなります。
非同期メソッドの方が簡単に記述できます
Visual Basic の Async キーワードと Await キーワードは、非同期プログラミングの中心です。 これら 2 つのキーワードを使用すると、.NET Framework または Windows ランタイムのリソースを使用して、同期メソッドを作成するのとほぼ同じくらい簡単に非同期メソッドを作成できます。
Async
とAwait
を使用して定義する非同期メソッドは、非同期メソッドと呼ばれます。
非同期メソッドの例を次に示します。 コード内のほぼすべてのものが、完全に使い慣れたものに見えるはずです。 コメントは、非同期を作成するために追加する機能を呼び出します。
このトピックの最後に、完全な Windows Presentation Foundation (WPF) サンプル ファイルがあり、Async サンプルからサンプルをダウンロードできます 。例については、「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://learn.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
AccessTheWebAsync
GetStringAsync
の呼び出しとその完了の待機の間に実行できる作業がない場合は、次の単一ステートメントを呼び出して待機することで、コードを簡略化できます。
Dim urlContents As String = Await client.GetStringAsync()
次の特性は、前の例を非同期メソッドにする内容をまとめたものです。
メソッド シグネチャには、
Async
修飾子が含まれています。非同期メソッドの名前は、慣例により末尾に "Async" サフィックスを付けます。
戻り値の型は、次のいずれかの型です。
- メソッドに戻り値ステートメントがあり、オペランドに TResult 型がある場合は Task(Of TResult)。
- Task メソッドに return ステートメントがない場合、またはオペランドのない return ステートメントがある場合。
- 非同期イベント ハンドラーを記述している場合は Sub。
詳細については、このトピックで後述する「戻り値の型とパラメーター」を参照してください。
通常、このメソッドには、待機中の非同期操作が完了するまでメソッドを続行できないポイントをマークする、少なくとも 1 つの await 式が含まれます。 その間、メソッドは中断され、コントロールはメソッドの呼び出し元に戻ります。 このトピックの次のセクションでは、中断ポイントでの動作について説明します。
非同期メソッドでは、指定されたキーワードと型を使用して何を行うかを示し、中断されたメソッドの待機ポイントに制御が戻ったときに何が起こるかを追跡するなど、コンパイラが残りの処理を行います。 ループや例外処理などの一部のルーチン プロセスは、従来の非同期コードでは処理が困難な場合があります。 非同期メソッドでは、同期ソリューションの場合と同じようにこれらの要素を記述すると、問題が解決されます。
以前のバージョンの .NET Framework での非同期性の詳細については、「 TPL と従来の .NET Framework 非同期プログラミング」を参照してください。
Async メソッドでの動作
非同期プログラミングで理解しておく必要がある最も重要なことは、制御フローがメソッドからメソッドにどのように移行されるかです。 次の図は、プロセスを示しています。
図の番号は、次の手順に対応しています。
イベント ハンドラーは、
AccessTheWebAsync
非同期メソッドを呼び出して待機します。AccessTheWebAsync
は、 HttpClient インスタンスを作成し、 GetStringAsync 非同期メソッドを呼び出して、Web サイトの内容を文字列としてダウンロードします。進行状況を中断する
GetStringAsync
で何かが発生します。 おそらく、Web サイトのダウンロードやその他のブロックアクティビティを待つ必要があります。 リソースのブロックを回避するために、GetStringAsync
は呼び出し元のAccessTheWebAsync
に制御を渡します。GetStringAsync
は 、TResult が文字列である Task(Of TResult) を返し、AccessTheWebAsync
はタスクをgetStringTask
変数に割り当てます。 タスクは、GetStringAsync
の呼び出しの進行中のプロセスを表し、作業が完了したときに実際の文字列値を生成することを約束します。getStringTask
はまだ待機していないため、AccessTheWebAsync
は、GetStringAsync
の最終結果に依存しない他の作業を続行できます。 この作業は、同期メソッドDoIndependentWork
の呼び出しによって表されます。DoIndependentWork
は、その処理を行い、呼び出し元に戻る同期メソッドです。AccessTheWebAsync
は、getStringTask
の結果なしで実行できる作業が不足しています。AccessTheWebAsync
次に、ダウンロードした文字列の長さを計算して返しますが、メソッドが文字列を持つまで、メソッドはその値を計算できません。そのため、
AccessTheWebAsync
は await 演算子を使用して進行状況を中断し、AccessTheWebAsync
を呼び出したメソッドに制御を与えます。AccessTheWebAsync
は、呼び出し元にTask(Of Integer)
を返します。 タスクは、ダウンロードした文字列の長さである整数の結果を生成する約束を表します。注
AccessTheWebAsync
が待機する前にGetStringAsync
(したがってgetStringTask
) が完了した場合、制御はAccessTheWebAsync
に残ります。 呼び出された非同期プロセス (getStringTask
) が既に完了していて、AccessTheWebSync が最終的な結果を待つ必要がない場合、中断してからAccessTheWebAsync
に戻る費用は無駄になります。呼び出し元 (この例のイベント ハンドラー) 内では、処理パターンが続行されます。 呼び出し元は、その結果を待機する前に、
AccessTheWebAsync
の結果に依存しない他の作業を行うか、呼び出し元がすぐに待機する可能性があります。 イベント ハンドラーはAccessTheWebAsync
を待機しており、AccessTheWebAsync
はGetStringAsync
を待機しています。GetStringAsync
が完了し、文字列の結果が生成されます。 文字列の結果は、期待した方法でGetStringAsync
を呼び出しても返されません。 (このメソッドは、手順 3 で既にタスクを返したことを思い出してください)。代わりに、文字列の結果は、メソッドの完了を表すタスクに格納getStringTask
。 await 演算子は、getStringTask
から結果を取得します。 assignment ステートメントは、取得した結果をurlContents
に割り当てます。AccessTheWebAsync
に文字列の結果がある場合、メソッドは文字列の長さを計算できます。 その後、AccessTheWebAsync
の作業も完了し、待機中のイベント ハンドラーを再開できます。 トピックの最後の完全な例では、イベント ハンドラーが長さの結果の値を取得して出力することを確認できます。
非同期プログラミングを初めて使用する場合は、同期動作と非同期動作の違いを考慮する必要があります。 同期メソッドは、作業が完了すると返されますが (手順 5)、非同期メソッドは、作業が中断されたときにタスク値を返します (手順 3 と 6)。 非同期メソッドが最終的にその作業を完了すると、タスクは完了としてマークされ、結果がある場合はタスクに格納されます。
制御フローの詳細については、「 非同期プログラムの制御フロー (Visual Basic)」を参照してください。
API 非同期メソッド
非同期プログラミングをサポートする GetStringAsync
などのメソッドがどこにあるか疑問に思うかもしれません。 .NET Framework 4.5 以降には、 Async
と Await
で動作する多くのメンバーが含まれています。 これらのメンバーは、メンバー名にアタッチされた "Async" サフィックスと、 Task または Task(Of TResult)の戻り値の型によって認識できます。 たとえば、System.IO.Stream
クラスには、CopyToAsync、ReadAsync、およびWriteAsyncの同期メソッドと共に、CopyTo、Read、Writeなどのメソッドが含まれます。
Windows ランタイムには、Windows アプリの Async
と Await
で使用できる多くのメソッドも含まれています。 詳細とメソッドの例については、「 C# または Visual Basic での非同期 API の呼び出し」、 非同期プログラミング (Windows ランタイム アプリ)、 WhenAny: .NET Framework と Windows ランタイム間のブリッジングに関する説明を参照してください。
スレッド数
非同期メソッドは、非ブロッキング操作を目的としています。 非同期メソッドの 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
修飾子にもかかわらず同期メソッドとして実行されます。 コンパイラは、このようなメソッドに対して警告を発行します。
Async
と Await
はコンテキスト キーワードです。 詳細と例については、次のトピックを参照してください。
戻り値の型とパラメーター
.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..」を参照してください。捕まえる。。。Finally ステートメント。
Windows ランタイム プログラミングの非同期 API には、タスクに似た次のいずれかの戻り値の型があります。
- Task(Of TResult) に対応する IAsyncOperation(Of TResult)、
- IAsyncActionに対応します。 Task
- IAsyncActionWithProgress(Of TProgress)
- IAsyncOperationWithProgress(Of TResult, TProgress)
詳細と例については、「 C# または Visual Basic での非同期 API の呼び出し」を参照してください。
名前付け規則
慣例により、 Async
修飾子を持つメソッドの名前に "Async" を追加します。
イベント、基底クラス、またはインターフェイス コントラクトが別の名前を提案する規則は無視できます。 たとえば、 Button1_Click
などの一般的なイベント ハンドラーの名前を変更しないでください。
関連トピックとサンプル (Visual Studio)
コード例全体
次のコードは、このトピックで説明する Windows Presentation Foundation (WPF) アプリケーションのMainWindow.xaml.vb ファイルです。 サンプルは、Async サンプルからダウンロードできます。 例は、"Async と Await を使用した非同期プログラミング" にあります。
Imports System.Net.Http
' Example that demonstrates Asynchronous Programming 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://learn.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
こちらも参照ください
.NET