.NET では、タスク ベースの非同期パターンは、新しい開発に推奨される非同期設計パターンです。 これは、非同期操作を表すために使用されるSystem.Threading.Tasks名前空間のTask型とTask<TResult>型に基づいています。
名前付け、パラメーター、および戻り値の型
TAP では、1 つのメソッドを使用して、非同期操作の開始と完了を表します。 これは、非同期プログラミング モデル (APM または IAsyncResult
) パターンとイベント ベースの非同期パターン (EAP) の両方と対照的です。 APM には、 Begin
メソッドと End
メソッドが必要です。 EAP には、 Async
サフィックスを持つメソッドが必要です。また、1 つ以上のイベント、イベント ハンドラー デリゲート型、および EventArg
派生型も必要です。 TAP の非同期メソッドには、Task、Task<TResult>、ValueTask、ValueTask<TResult>など、待機可能な型を返すメソッドの操作名の後に、Async
サフィックスが含まれます。 たとえば、Task<String>
を返す非同期Get
操作には、GetAsync
という名前を付けることができます。 サフィックスを持つ EAP メソッド名が既に含まれているクラスに TAP メソッドを追加する場合は、代わりにサフィックス を使用します。 たとえば、クラスに既に GetAsync
メソッドがある場合は、 GetTaskAsync
という名前を使用します。 メソッドが非同期操作を開始しても待機可能な型を返さない場合、その名前は Begin
、 Start
、またはその他の動詞で始まり、このメソッドが操作の結果を返したりスローしたりしないことを示唆する必要があります。
TAP メソッドは、対応する同期メソッドが void または型TResult
を返すかどうかに基づいて、System.Threading.Tasks.TaskまたはSystem.Threading.Tasks.Task<TResult>を返します。
TAP メソッドのパラメーターは、対応する同期メソッドのパラメーターと一致し、同じ順序で指定する必要があります。 ただし、 out
パラメーターと ref
パラメーターは、この規則から除外されるため、完全に回避する必要があります。
out
またはref
パラメーターを使用して返されたデータは、代わりにTask<TResult>によって返されるTResult
の一部として返される必要があります。また、複数の値を格納するには、タプルまたはカスタム データ構造を使用する必要があります。 また、TAP メソッドの同期パラメーターに対応するパラメーターが提供されていない場合でも、 CancellationToken パラメーターを追加することを検討してください。
タスクの作成、操作、または組み合わせ専用のメソッド (メソッドの非同期の意図がメソッド名またはメソッドが属する型の名前で明確である場合) は、この名前付けパターンに従う必要はありません。このようなメソッドは、多くの場合、 コンバイネーターと呼ばれます。 組み合わせ子の例としては、WhenAllとWhenAnyがあり、「タスク ベースの非同期パターンを使用する」の記事の「組み込みのタスク ベースのコンバイネータの使用」セクションで説明されています。
TAP 構文と、非同期プログラミング モデル (APM) やイベント ベースの非同期パターン (EAP) などの従来の非同期プログラミング パターンで使用される構文との違いの例については、「 非同期プログラミング パターン」を参照してください。
非同期操作の開始
TAP に基づく非同期メソッドは、結果のタスクを返す前に、引数の検証や非同期操作の開始など、少量の作業を同期的に実行できます。 非同期メソッドがすばやく戻ることができるように、同期処理は最小限に抑える必要があります。 クイック リターンの理由は次のとおりです。
非同期メソッドはユーザー インターフェイス (UI) スレッドから呼び出される可能性があり、実行時間の長い同期作業はアプリケーションの応答性に悪影響を与える可能性があります。
複数の非同期メソッドを同時に起動できます。 そのため、非同期メソッドの同期部分で実行時間の長い作業を行う場合、他の非同期操作の開始が遅れる可能性があるため、コンカレンシーの利点が低下する可能性があります。
場合によっては、操作を完了するために必要な作業量が、操作を非同期的に起動するために必要な作業量よりも少なくなります。 既にメモリにバッファリングされているデータによって読み取り操作を満たすことができるストリームからの読み取りは、このようなシナリオの例です。 このような場合、操作は同期的に完了し、既に完了しているタスクを返す場合があります。
例外
非同期メソッドは、使用エラーに応答する場合にのみ、非同期メソッド呼び出しからスローされる例外を発生させる必要があります。 運用環境のコードで使用エラーが発生することはありません。 たとえば、メソッドの引数の 1 つとして null 参照 (Visual Basic でNothing
) を渡すと、エラー状態 (通常は ArgumentNullException 例外で表されます) が発生する場合は、呼び出し元のコードを変更して、null 参照が渡されないようにすることができます。 他のすべてのエラーでは、非同期メソッドが実行されているときに発生する例外は、タスクが返される前に非同期メソッドが同期的に完了した場合でも、返されるタスクに割り当てる必要があります。 通常、タスクには最大で 1 つの例外が含まれます。 ただし、タスクが複数の操作 (たとえば、 WhenAll) を表す場合、複数の例外が 1 つのタスクに関連付けられている可能性があります。
ターゲット環境
TAP メソッドを実装すると、非同期実行の発生場所を特定できます。 スレッド プールでワークロードを実行するか、非同期 I/O を使用して実装するか (操作の実行の大部分をスレッドにバインドせずに)、特定のスレッド (UI スレッドなど) で実行するか、任意の数の潜在的なコンテキストを使用することができます。 TAP メソッドは何も実行する必要がない場合もあります。また、システム内の他の場所で条件が発生したことを表す Task を返す場合もあります (たとえば、キューに置かれたデータ構造に到着するデータを表すタスク)。
TAP メソッドの呼び出し元は、結果のタスクを同期的に待機して TAP メソッドの完了を待機するのをブロックしたり、非同期操作が完了したときに追加の (継続) コードを実行したりする場合があります。 継続コードの作成者は、そのコードの実行場所を制御できます。 継続コードは、 Task クラスのメソッド (たとえば、 ContinueWith) を使用して明示的に作成することも、継続に基づいて構築された言語サポート (C# では await
、Visual Basic では Await
、F# では AwaitValue
) を使用して暗黙的に作成することもできます。
タスクの状態
Task クラスは非同期操作のライフ サイクルを提供し、そのサイクルはTaskStatus列挙体によって表されます。 TaskとTask<TResult>から派生する型のコーナー ケースをサポートし、構築とスケジューリングの分離をサポートするために、Task クラスはStart メソッドを公開します。 パブリック Task コンストラクターによって作成されたタスクは、スケジュールされていないCreated状態でライフ サイクルを開始し、これらのインスタンスでStartが呼び出されたときにのみスケジュールされるため、コールド タスクと呼ばれます。
他のすべてのタスクは、ライフ サイクルをホットな状態で開始します。つまり、それらが表す非同期操作は既に開始されており、タスクの状態は TaskStatus.Created以外の列挙値です。 TAP メソッドから返されるすべてのタスクをアクティブにする必要があります。 TAP メソッドが内部的にタスクのコンストラクターを使用して返されるタスクをインスタンス化する場合、TAP メソッドは、Task オブジェクトに対してStartを呼び出してから返す必要があります。 TAP メソッドのコンシューマーは、返されたタスクがアクティブであり、TAP メソッドから返されたTaskに対してStartを呼び出そうとしないことを安全に想定できます。 アクティブなタスクで Start を呼び出すと、 InvalidOperationException 例外が発生します。
キャンセル (省略可能)
TAP では、非同期メソッド実装者と非同期メソッド コンシューマーの両方に対してキャンセルは省略可能です。 操作で取り消しが許可されている場合、キャンセル トークン (CancellationToken インスタンス) を受け取る非同期メソッドのオーバーロードが公開されます。 慣例により、パラメーターには cancellationToken
という名前が付けられます。
public Task ReadAsync(byte [] buffer, int offset, int count,
CancellationToken cancellationToken)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
count As Integer,
cancellationToken As CancellationToken) _
As Task
非同期操作では、キャンセル要求についてこのトークンを監視します。 取り消し要求を受け取った場合は、その要求を受け入れ、操作を取り消すことができます。 取り消し要求の結果、作業が途中で終了した場合、TAP メソッドは Canceled 状態で終了するタスクを返します。使用可能な結果はなく、例外はスローされません。
Canceled状態は、FaultedおよびRanToCompletion状態と共に、タスクの最終 (完了) 状態と見なされます。 したがって、タスクが Canceled 状態の場合、その IsCompleted プロパティは true
を返します。 タスクが Canceled 状態で完了すると、継続をオプトアウトするように NotOnCanceled などの継続オプションが指定されていない限り、タスクに登録されたすべての継続がスケジュールまたは実行されます。 言語機能を使用して取り消されたタスクを非同期的に待機しているコードは、引き続き実行されますが、 OperationCanceledException またはそこから派生した例外を受け取ります。
WaitやWaitAllなどのメソッドを使用してタスクを同期的に待機しているコードも、例外を伴って引き続き実行されます。
取り消しトークンが、そのトークンを受け入れる TAP メソッドが呼び出される前に取り消しを要求した場合、TAP メソッドは Canceled タスクを返す必要があります。 ただし、非同期操作の実行中に取り消しが要求された場合、非同期操作は取り消し要求を受け入れる必要はありません。 返されるタスクは、取り消し要求の結果として操作が終了した場合にのみ、 Canceled 状態で終了する必要があります。 取り消しが要求されても、結果または例外が生成された場合、タスクは RanToCompletion または Faulted 状態で終了する必要があります。
最初にキャンセルする機能を公開する非同期メソッドの場合、キャンセル トークンを受け入れないオーバーロードを指定する必要はありません。 キャンセルできないメソッドの場合は、キャンセル トークンを受け入れるオーバーロードを指定しないでください。これは、ターゲット メソッドが実際にキャンセル可能かどうかを呼び出し元に示すのに役立ちます。 取り消しを望まないコンシューマー コードは、 CancellationToken を受け取り、引数値として None を提供するメソッドを呼び出す場合があります。 None は、機能的には既定の CancellationTokenと同等です。
進行状況レポート (省略可能)
一部の非同期操作は、進行状況通知を提供することでメリットがあります。これらは通常、非同期操作の進行状況に関する情報を使用してユーザー インターフェイスを更新するために使用されます。
TAP では、進行状況は IProgress<T> インターフェイスを介して処理されます。このインターフェイスは、通常は progress
という名前のパラメーターとして非同期メソッドに渡されます。 非同期メソッドが呼び出されたときに進行状況インターフェイスを指定すると、不適切な使用 (つまり、操作の開始後に誤って登録されたイベント ハンドラーが更新プログラムを見逃す可能性がある) による競合状態を排除するのに役立ちます。 さらに重要なのは、進行状況インターフェイスは、使用するコードによって決定される、さまざまな進行状況の実装をサポートすることです。 たとえば、使用するコードは、最新の進行状況の更新のみを考慮する場合や、すべての更新プログラムをバッファーに格納したい場合や、更新ごとにアクションを呼び出したい場合や、呼び出しが特定のスレッドにマーシャリングされるかどうかを制御したい場合があります。 これらのオプションはすべて、特定のコンシューマーのニーズに合わせてカスタマイズされたインターフェイスの異なる実装を使用して実現できます。 取り消しと同様に、TAP 実装では、API が進行状況通知をサポートしている場合にのみ、 IProgress<T> パラメーターを指定する必要があります。
たとえば、この記事で前に説明した ReadAsync
メソッドが、これまでに読み取ったバイト数の形式で中間進行状況を報告できる場合、進行状況コールバックは IProgress<T> インターフェイスである可能性があります。
public Task ReadAsync(byte[] buffer, int offset, int count,
IProgress<long> progress)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
count As Integer,
progress As IProgress(Of Long)) As Task
FindFilesAsync
メソッドが特定の検索パターンを満たすすべてのファイルの一覧を返す場合、進行状況コールバックは、完了した作業の割合と現在の部分的な結果のセットの見積もりを提供できます。 この情報には、次のいずれかのタプルを指定できます。
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<Tuple<double,
ReadOnlyCollection<List<FileInfo>>>> progress)
Public Function FindFilesAsync(pattern As String,
progress As IProgress(Of Tuple(Of Double, ReadOnlyCollection(Of List(Of FileInfo))))) _
As Task(Of ReadOnlyCollection(Of FileInfo))
または、API に固有のデータ型を使用します。
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<FindFilesProgressInfo> progress)
Public Function FindFilesAsync(pattern As String,
progress As IProgress(Of FindFilesProgressInfo)) _
As Task(Of ReadOnlyCollection(Of FileInfo))
後者の場合、通常、特殊なデータ型のサフィックスは ProgressInfo
です。
TAP 実装が、 progress
パラメーターを受け取るオーバーロードを提供する場合は、引数を null
できるようにする必要があります。その場合、進行状況は報告されません。 TAP 実装では、 Progress<T> オブジェクトに進行状況を同期的に報告する必要があります。これにより、非同期メソッドで進行状況をすばやく提供できます。 また、進行状況のコンシューマーは、情報を処理する方法と最適な場所を決定することもできます。 たとえば、進行状況インスタンスは、コールバックをマーシャリングし、キャプチャされた同期コンテキストでイベントを発生することを選択できます。
IProgress<T> 実装
.NET には、IProgress<T>を実装するProgress<T> クラスが用意されています。 Progress<T> クラスは次のように宣言されます。
public class Progress<T> : IProgress<T>
{
public Progress();
public Progress(Action<T> handler);
protected virtual void OnReport(T value);
public event EventHandler<T>? ProgressChanged;
}
Progress<T>のインスタンスは、非同期操作が進行状況の更新を報告するたびに発生するProgressChanged イベントを公開します。 ProgressChanged イベントは、Progress<T> インスタンスがインスタンス化されたときにキャプチャされたSynchronizationContext オブジェクトで発生します。 使用できる同期コンテキストがない場合は、スレッド プールを対象とする既定のコンテキストが使用されます。 ハンドラーは、このイベントに登録できます。 便宜上、1 つのハンドラーを Progress<T> コンストラクターに提供することもできます。また、 ProgressChanged イベントのイベント ハンドラーと同様に動作します。 進行状況の更新は、イベント ハンドラーの実行中に非同期操作が遅れないように非同期的に発生します。 別の IProgress<T> 実装では、異なるセマンティクスを適用することを選択できます。
提供するオーバーロードの選択
TAP 実装で省略可能な CancellationToken パラメーターと省略可能な IProgress<T> パラメーターの両方を使用する場合、最大 4 つのオーバーロードが必要になる可能性があります。
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
public Task MethodNameAsync(…, IProgress<T> progress);
public Task MethodNameAsync(…,
CancellationToken cancellationToken, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken cancellationToken) As Task
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
progress As IProgress(Of T)) As Task
ただし、多くの TAP 実装では取り消しや進行状況の機能が提供されないため、1 つの方法が必要です。
public Task MethodNameAsync(…);
Public MethodNameAsync(…) As Task
TAP 実装でキャンセルまたは進行状況のいずれかがサポートされているが、両方がサポートされていない場合は、次の 2 つのオーバーロードが提供される可能性があります。
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
// … or …
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken) As Task
' … or …
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
TAP 実装で取り消しと進行状況の両方がサポートされている場合、4 つのオーバーロードすべてが公開される可能性があります。 ただし、次の 2 つだけを提供できます。
public Task MethodNameAsync(…);
public Task MethodNameAsync(…,
CancellationToken cancellationToken, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
progress As IProgress(Of T)) As Task
不足している 2 つの中間の組み合わせを補うために、開発者は、cancellationToken
パラメーターに対してNoneまたは既定のCancellationTokenを渡し、progress
パラメーターのnull
を渡すことができます。
TAP メソッドのすべての使用がキャンセルまたは進行状況をサポートすることが予想される場合は、関連するパラメーターを受け入れないオーバーロードを省略できます。
取り消しまたは進行状況を省略可能にするために複数のオーバーロードを公開する場合、取り消しまたは進行状況をサポートしていないオーバーロードは、取り消しまたは進行状況のために None 渡されたかのように動作し、これらをサポートするオーバーロードに null
する必要があります。
関連資料
タイトル | 説明 |
---|---|
非同期プログラミング パターン | 非同期操作を実行するための 3 つのパターンについて説明します。タスク ベースの非同期パターン (TAP)、非同期プログラミング モデル (APM)、イベントベースの非同期パターン (EAP) です。 |
タスク ベースの非同期パターンの実装 | タスク ベースの非同期パターン (TAP) を実装する方法について説明します。Visual Studio で C# コンパイラと Visual Basic コンパイラを使用するか、手動で使用するか、コンパイラと手動メソッドの組み合わせを使用します。 |
タスク ベースの非同期パターンの使用 | タスクとコールバックを使用して、ブロックせずに待機を実現する方法について説明します。 |
他の非同期パターンと型との相互運用 | タスク ベースの非同期パターン (TAP) を使用して、非同期プログラミング モデル (APM) とイベント ベースの非同期パターン (EAP) を実装する方法について説明します。 |
.NET