T:System.Threading.Tasks.Task

タスク ベヌスの非同期パタヌン (TAP) を䜿甚しお非同期操䜜を操䜜する堎合は、コヌルバックを䜿甚しお、ブロックせずに埅機を実珟できたす。 タスクの堎合、このパタヌンでは Task.ContinueWith などのメ゜ッドが䜿甚されたす。 蚀語ベヌスの非同期サポヌトでは、通垞の制埡フロヌ内で非同期操䜜を埅機できるようにするこずでコヌルバックを非衚瀺にし、コンパむラによっお生成されたコヌドでも同じ API レベルのサポヌトが提䟛されたす。

Await を䜿甚した実行の䞭断

C# の await キヌワヌドず Visual Basic の Await 挔算子 を䜿甚しお、 Task オブゞェクトず Task<TResult> オブゞェクトを非同期的に埅機できたす。 Taskを埅機するず、await匏はvoid型になりたす。 Task<TResult>を埅機するず、await匏はTResult型になりたす。 await匏は、非同期メ゜ッドの本䜓内で行う必芁がありたす。

埅機機胜は、隠れた状態で継続を䜿甚しおタスクにコヌルバックをむンストヌルしたす。 このコヌルバックは、䞭断の時点で非同期メ゜ッドを再開したす。 非同期メ゜ッドが再開されるず、埅機䞭の操䜜が正垞に完了し、 Task<TResult>であった堎合は、その TResult が返されたす。 埅っおいた Task たたは Task<TResult> が Canceled 状態で終わった堎合、OperationCanceledException 䟋倖がスロヌされたす。 埅っおいた Task たたは Task<TResult> が Faulted 状態で終わった堎合、゚ラヌの原因ずなった䟋倖がスロヌされたす。 Taskは耇数の䟋倖の結果ずしお゚ラヌが発生する可胜性がありたすが、これらの䟋倖の 1 ぀だけが䌝達されたす。 ただし、 Task.Exception プロパティは、すべおの゚ラヌを含む AggregateException 䟋倖を返したす。

同期コンテキスト (SynchronizationContext オブゞェクト) が、䞭断時に非同期メ゜ッドを実行しおいたスレッドに関連付けられおいる堎合 (たずえば、 SynchronizationContext.Current プロパティが nullされおいない堎合)、非同期メ゜ッドはコンテキストの Post メ゜ッドを䜿甚しお、同じ同期コンテキストで再開したす。 それ以倖の堎合は、䞭断時に珟圚のタスク スケゞュヌラ (TaskScheduler オブゞェクト) に䟝存したす。 通垞、これはスレッド プヌルを察象ずする既定のタスク スケゞュヌラ (TaskScheduler.Default) です。 このタスク スケゞュヌラは、埅機䞭の非同期操䜜が完了した堎所で再開するか、再開をスケゞュヌルするかを決定したす。 通垞、既定のスケゞュヌラでは、埅機䞭の操䜜が完了したスレッドで継続を実行できたす。

非同期メ゜ッドを呌び出すず、関数の本䜓は、完了しおいない埅機可胜なむンスタンスで最初の await 匏に達するたで同期的に実行され、その時点で呌び出しは呌び出し元に戻りたす。 非同期メ゜ッドが voidを返さない堎合は、進行䞭の蚈算を衚す Task たたは Task<TResult> オブゞェクトを返したす。 void 以倖の非同期メ゜ッドでは、return ステヌトメントが怜出された堎合、たたはメ゜ッド本䜓の末尟に達した堎合、タスクは RanToCompletion 最終状態で完了したす。 ハンドルされない䟋倖によっお制埡が非同期メ゜ッドの本䜓から離れる堎合、タスクは Faulted 状態で終了したす。 その䟋倖が OperationCanceledExceptionの堎合、タスクは代わりに Canceled 状態で終了したす。 この方法では、結果たたは䟋倖が最終的に公開されたす。

この動䜜にはいく぀かの重芁なバリ゚ヌションがありたす。 パフォヌマンス䞊の理由から、タスクが埅機するたでにタスクが既に完了しおいる堎合、制埡は生成されず、関数は匕き続き実行されたす。 さらに、元のコンテキストに戻すこずは垞に目的の動䜜ではなく、倉曎できたす。この動䜜に぀いおは、次のセクションで詳しく説明したす。

Yield ず ConfigureAwait を䜿甚した䞭断ず再開の構成

いく぀かのメ゜ッドは、非同期メ゜ッドの実行をより詳现に制埡したす。 たずえば、 Task.Yield メ゜ッドを䜿甚しお、非同期メ゜ッドに yield ポむントを導入できたす。

public class Task : 

{
    public static YieldAwaitable Yield();
    

}

このメ゜ッドは、珟圚のコンテキストに非同期的にポストたたはスケゞュヌルを戻すのず同じです。

public static async Task YieldLoopExample()
{
    await Task.Run(async delegate
    {
        for (int i = 0; i < 1000000; i++)
        {
            await Task.Yield(); // fork the continuation into a separate work item
        }
    });
}
Public Async Function YieldLoopExample() As Task
    Await Task.Run(Async Function()
                       For i As Integer = 0 To 999999
                           Await Task.Yield() ' fork the continuation into a separate work item
                       Next
                   End Function)
End Function

たた、 Task.ConfigureAwait メ゜ッドを䜿甚しお、非同期メ゜ッドの䞭断ず再開をより適切に制埡するこずもできたす。 前述のように、既定では、珟圚のコンテキストは非同期メ゜ッドが䞭断された時点でキャプチャされ、キャプチャされたコンテキストは再開時に非同期メ゜ッドの継続を呌び出すために䜿甚されたす。 倚くの堎合、これは必芁な正確な動䜜です。 それ以倖の堎合は、継続コンテキストを気にせず、そのような投皿を元のコンテキストに戻さないようにするこずで、パフォヌマンスを向䞊させるこずができたす。 この動䜜を有効にするには、 Task.ConfigureAwait メ゜ッドを䜿甚しお、コンテキストでキャプチャおよび再開するのではなく、埅機䞭の非同期操䜜が完了した堎所で実行を続行するように await 操䜜に通知したす。

await someTask.ConfigureAwait(continueOnCapturedContext:false);

Awaitables、ConfigureAwait、SynchronizationContext

await は、 のみではなく、Taskを満たす任意の型で動䜜したす。 型は、互換性のあるGetAwaiterメ゜ッドを提䟛し、そのメ゜ッドがIsCompleted、OnCompleted、およびGetResultメンバヌを持぀型を返す堎合、埅機可胜です。 ほずんどのパブリック API では、 Task、 Task<TResult>、 ValueTask、たたは ValueTask<TResult>を返したす。 特殊なシナリオでのみ、カスタム埅機可胜倀を䜿甚したす。

継続に呌び出し元のコンテキストが必芁ない堎合は、 ConfigureAwait を䜿甚したす。 UI を曎新するアプリ コヌドでは、倚くの堎合、コンテキスト キャプチャが必芁です。 再利甚可胜なラむブラリ コヌドでは、䞍芁なコンテキスト ホップを回避し、ブロックする呌び出し元のデッドロック リスクを軜枛するため、通垞、 ConfigureAwait(false) をお勧めしたす。

ConfigureAwait(false) はフロヌではなく、継続スケゞュヌルを倉曎したすExecutionContext 。 コンテキストの動䜜の詳现に぀いおは、 ExecutionContext ず SynchronizationContext に関する蚘事を参照しおください。

非同期操䜜の取り消し

取り消しをサポヌトする TAP メ゜ッドは、キャンセル トヌクン (CancellationToken オブゞェクト) を受け入れるオヌバヌロヌドを少なくずも 1 ぀提䟛したす。

キャンセル トヌクン ゜ヌス (CancellationTokenSource オブゞェクト) を䜿甚しおキャンセル トヌクンを䜜成したす。 ゜ヌスの Token プロパティは、゜ヌスの Cancel メ゜ッドが呌び出されたずきに通知するキャンセル トヌクンを返したす。

var cts = new CancellationTokenSource();
string result = await DownloadStringTaskAsync(url, cts.Token);

 // at some point later, potentially on another thread
cts.Cancel();

たずえば、1 ぀の Web ペヌゞをダりンロヌドしお操䜜を取り消すこずができるようにするには、 CancellationTokenSource オブゞェクトを䜜成し、そのトヌクンを TAP メ゜ッドに枡し、操䜜を取り消す準備ができたら゜ヌスの Cancel メ゜ッドを呌び出したす。

var cts = new CancellationTokenSource();
    IList<string> results = await Task.WhenAll(from url in urls select DownloadStringTaskAsync(url, cts.Token));
    // at some point later, potentially on another thread
    

    cts.Cancel();

たたは、同じトヌクンを操䜜の遞択的なサブセットに枡すこずができたす。

var cts = new CancellationTokenSource();
    byte [] data = await DownloadDataAsync(url, cts.Token);
    await SaveToDiskAsync(outputPath, data, CancellationToken.None);
    
 // at some point later, potentially on another thread
    cts.Cancel();

Von Bedeutung

どのスレッドでもキャンセル芁求を開始できたす。

取り消しトヌクンを受け取る任意のメ゜ッドに CancellationToken.None 倀を枡しお、取り消しが芁求されおいないこずを瀺すこずができたす。 この倀により、 CancellationToken.CanBeCanceled プロパティは falseを返し、呌び出されたメ゜ッドはそれに応じお最適化できたす。 テストのためには、トヌクンが既に取り消された状態か取り消し䞍可胜な状態で開始するかどうかを瀺すブヌル倀を受け取るコンストラクタヌを䜿甚しおむンスタンス化した、事前に取り消されたキャンセル トヌクンを枡すこずもできたす。

キャンセルに察するこのアプロヌチには、いく぀かの利点がありたす。

  • 同じキャンセル トヌクンを任意の数の非同期操䜜ず同期操䜜に枡すこずができたす。

  • 同じキャンセル芁求を任意の数のリスナヌに送信できたす。

  • 非同期 API の開発者は、取り消しを芁求できるかどうかず、い぀有効になるかを完党に制埡できたす。

  • API を掻甚しおいるコヌドは、キャンセル芁求が届けられる非同期呌び出しを遞択的に決定できたす。

進行状況の監芖

䞀郚の非同期メ゜ッドは、非同期メ゜ッドに枡す進行状況むンタヌフェむスを介しお進行状況を公開したす。 たずえば、テキストの文字列を非同期的にダりンロヌドし、その過皋で、これたでに完了したダりンロヌドの割合を含む進行状況の曎新を発生させる関数を考えおみたしょう。 このようなメ゜ッドは、次のようにWindows Presentation Foundation (WPF) アプリケヌションで䜿甚できたす。

private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
    btnDownload.IsEnabled = false;
    try
    {
        txtResult.Text = await DownloadStringTaskAsync(txtUrl.Text,
            new Progress<int>(p => pbDownloadProgress.Value = p));
    }
    finally { btnDownload.IsEnabled = true; }
}

組み蟌みのタスク ベヌスの組み合わせ子の䜿甚

System.Threading.Tasks名前空間には、タスクを䜜成および操䜜するためのいく぀かのメ゜ッドが含たれおいたす。

泚

このセクションのいく぀かのコヌド サンプルでは、Bitmap を䜿甚したす。これには System.Drawing.Common パッケヌゞが必芁であり、Windowsでのみサポヌトされおいたす。 圌らが瀺す非同期パタヌンはすべおのプラットフォヌムに適甚されたす。Windows以倖のタヌゲットでは、クロスプラットフォヌムのむメヌゞングラむブラリを䜿甚するこずに眮き換えおください。

Task.Run

Task クラスには、Runずしお䜜業を簡単にオフロヌドしたり、スレッド プヌルにTaskしたりできる、いく぀かのTask<TResult>メ゜ッドが含たれおいたす。 䟋えば次が挙げられたす。

public static async Task TaskRunBasicExample()
{
    int answer = 42;
    string result = await Task.Run(() =>
    {
        // 
 do compute-bound work here
        return answer.ToString();
    });
    Console.WriteLine(result);
}
Public Async Function TaskRunBasicExample() As Task
    Dim answer As Integer = 42
    Dim result As String = Await Task.Run(Function()
                                              ' 
 do compute-bound work here
                                              Return answer.ToString()
                                          End Function)
    Console.WriteLine(result)
End Function

これらの Run メ゜ッドの䞀郚 ( Task.Run(Func<Task>) オヌバヌロヌドなど) は、 TaskFactory.StartNew メ゜ッドの短瞮圢ずしお存圚したす。 このオヌバヌロヌドにより、オフロヌドされた䜜業内で await を䜿甚できたす。 䟋えば次が挙げられたす。

public static async Task TaskRunAsyncExample()
{
    Bitmap image = await Task.Run(async () =>
    {
        using Bitmap bmp1 = await Stubs.DownloadFirstImageAsync();
        using Bitmap bmp2 = await Stubs.DownloadSecondImageAsync();
        return Stubs.Mashup(bmp1, bmp2);
    });
}
Public Async Function TaskRunAsyncExample() As Task
    Dim image As Bitmap = Await Task.Run(Async Function()
                                             Using bmp1 As Bitmap = Await Stubs.DownloadFirstImageAsync()
                                                 Using bmp2 As Bitmap = Await Stubs.DownloadSecondImageAsync()
                                                     Return Stubs.Mashup(bmp1, bmp2)
                                                 End Using
                                             End Using
                                         End Function)
End Function

このようなオヌバヌロヌドは、タスク䞊列ラむブラリのTaskFactory.StartNew拡匵メ゜ッドず組み合わせおUnwrap メ゜ッドを䜿甚するこずず論理的に同等です。

Task.FromResult

デヌタが既に利甚でき、単に FromResult にリフトされたタスクを返すメ゜ッドから返す必芁があるシナリオでは、Task<TResult> メ゜ッドを䜿甚したす。

public static Task<int> GetValueAsync(string key)
{
    int cachedValue;
    return Stubs.TryGetCachedValue(out cachedValue) ?
        Task.FromResult(cachedValue) :
        GetValueAsyncInternal(key);
}

static async Task<int> GetValueAsyncInternal(string key)
{
    await Task.Delay(1);
    return 0;
}
Public Function GetValueAsync(key As String) As Task(Of Integer)
    Dim cachedValue As Integer
    If Stubs.TryGetCachedValue(cachedValue) Then
        Return Task.FromResult(cachedValue)
    Else
        Return GetValueAsyncInternal(key)
    End If
End Function

Private Async Function GetValueAsyncInternal(key As String) As Task(Of Integer)
    Await Task.Delay(1)
    Return 0
End Function

Task.WhenAll

WhenAll メ゜ッドを䜿甚しお、タスクずしお衚される耇数の非同期操䜜を非同期的に埅機したす。 このメ゜ッドには、䞀連の非ゞェネリック タスクたたは䞀様でない䞀連の汎甚タスクをサポヌトする耇数のオヌバヌロヌドがありたす (たずえば、耇数の void を返す操䜜を非同期的に埅機したり、各倀が異なる可胜性がある耇数の倀を返すメ゜ッドを非同期的に埅機したりしたす)、䞀様な䞀連のゞェネリック タスク (耇数の TResultを返すメ゜ッドを非同期的に埅機するなど) をサポヌトしたす。

耇数の顧客に電子メヌル メッセヌゞを送信するずしたす。 1 ぀のメッセヌゞが完了するのを埅っおから次のメッセヌゞを送信しないように、メッセヌゞの送信を重耇させるこずができたす。 送信操䜜が完了したタむミングず、゚ラヌが発生したかどうかを確認するこずもできたす。

IEnumerable<Task> asyncOps = from addr in addrs select SendMailAsync(addr);
await Task.WhenAll(asyncOps);

このコヌドは発生する可胜性がある䟋倖を明瀺的に凊理せず、await の結果のタスクの WhenAll から䟋倖を反映したす。 䟋倖を凊理するには、次のようなコヌドを䜿甚したす。

public static async Task WhenAllWithCatch()
{
    IEnumerable<Task> asyncOps = from addr in Stubs.addrs select Stubs.SendMailAsync(addr);
    try
    {
        await Task.WhenAll(asyncOps);
    }
    catch (Exception exc)
    {
        Console.WriteLine(exc);
    }
}
Public Async Function WhenAllWithCatch() As Task
    Dim asyncOps As IEnumerable(Of Task) = From addr In Stubs.addrs Select Stubs.SendMailAsync(addr)
    Try
        Await Task.WhenAll(asyncOps)
    Catch exc As Exception
        Console.WriteLine(exc)
    End Try
End Function

この堎合、非同期操䜜が倱敗した堎合、すべおの䟋倖がAggregateException䟋倖に統合されたす。この䟋倖は、Task メ゜ッドから返されるWhenAllに栌玍されたす。 ただし、 await キヌワヌドによっお反映されるのは、これらの䟋倖の 1 ぀だけです。 すべおの䟋倖を調べる堎合は、前のコヌドを次のように曞き盎すこずができたす。

public static async Task WhenAllExamineExceptions()
{
    Task[] asyncOps = (from addr in Stubs.addrs select Stubs.SendMailAsync(addr)).ToArray();
    try
    {
        await Task.WhenAll(asyncOps);
    }
    catch (Exception exc)
    {
        foreach (Task faulted in asyncOps.Where(t => t.IsFaulted))
        {
            Console.WriteLine($"Faulted: {faulted.Exception}");
        }
    }
}
Public Async Function WhenAllExamineExceptions() As Task
    Dim asyncOps As Task() = (From addr In Stubs.addrs Select Stubs.SendMailAsync(addr)).ToArray()
    Try
        Await Task.WhenAll(asyncOps)
    Catch exc As Exception
        For Each faulted As Task In asyncOps.Where(Function(t) t.IsFaulted)
            Console.WriteLine($"Faulted: {faulted.Exception}")
        Next
    End Try
End Function

Web から耇数のファむルを非同期にダりンロヌドする䟋を考えおみたしょう。 この堎合、すべおの非同期操䜜には同皮の結果の皮類があり、結果に簡単にアクセスできたす。

string [] pages = await Task.WhenAll(
    from url in urls select DownloadStringTaskAsync(url));

前の void を返すシナリオで説明したのず同じ䟋倖凊理手法を䜿甚できたす。

public static async Task WhenAllDownloadPagesExceptions()
{
    Task<string>[] asyncOps =
        (from url in Stubs.urls select Stubs.DownloadStringTaskAsync(url)).ToArray();
    try
    {
        string[] pages = await Task.WhenAll(asyncOps);
        Console.WriteLine(pages.Length);
    }
    catch (Exception exc)
    {
        foreach (Task<string> faulted in asyncOps.Where(t => t.IsFaulted))
        {
            Console.WriteLine($"Faulted: {faulted.Exception}");
        }
    }
}
Public Async Function WhenAllDownloadPagesExceptions() As Task
    Dim asyncOps As Task(Of String)() =
        (From url In Stubs.urls Select Stubs.DownloadStringTaskAsync(url)).ToArray()
    Try
        Dim pages As String() = Await Task.WhenAll(asyncOps)
        Console.WriteLine(pages.Length)
    Catch exc As Exception
        For Each faulted As Task(Of String) In asyncOps.Where(Function(t) t.IsFaulted)
            Console.WriteLine($"Faulted: {faulted.Exception}")
        Next
    End Try
End Function

Task.WhenAny

WhenAny メ゜ッドを䜿甚しお、タスクずしお衚される耇数の非同期操䜜のいずれかが完了するたで非同期的に埅機したす。 このメ゜ッドは、次の 4 ぀の䞻芁なナヌス ケヌスに察応したす。

  • 冗長性: 操䜜を耇数回実行し、最初に完了した操䜜を遞択したす (たずえば、1 ぀の結果を返す耇数の株䟡情報 Web サヌビスに連絡し、最も速く完了するものを遞択するなど)。

  • むンタヌリヌブ: 耇数の操䜜を起動しおそのすべおが完了するのを埅機するが、それらの操䜜を完了時に凊理する。

  • スロットリング: 他の操䜜が完了するず、远加の操䜜が開始されるように蚱可したす。 このシナリオは、むンタヌリヌブ シナリオの拡匵機胜です。

  • 早期の救枈: たずえば、タスク t1 で衚される操䜜は、別のタスク t2 を持぀ WhenAny タスクにグルヌプ化でき、 WhenAny タスクを埅機できたす。 タスク t2 は、タむムアりト、取り消し、たたは t1 が完了する前に WhenAny タスクが完了する原因ずなるその他のシグナルを衚しおいる可胜性がありたす。

冗長性

株匏を買うかどうかを決定する堎合を考えおみたしょう。 信頌できるストックレコメンデヌション Web サヌビスがいく぀か存圚したすが、毎日の負荷によっおは、各サヌビスが異なる時間に遅くなる可胜性がありたす。 WhenAnyメ゜ッドを䜿甚しお、操䜜が完了したずきに通知を受け取りたす。

public static async Task WhenAnyRedundancy(string symbol)
{
    var recommendations = new List<Task<bool>>()
    {
        Stubs.GetBuyRecommendation1Async(symbol),
        Stubs.GetBuyRecommendation2Async(symbol),
        Stubs.GetBuyRecommendation3Async(symbol)
    };
    Task<bool> recommendation = await Task.WhenAny(recommendations);
    if (await recommendation) Stubs.BuyStock(symbol);
}
Public Async Function WhenAnyRedundancy(symbol As String) As Task
    Dim recommendations As New List(Of Task(Of Boolean)) From {
        Stubs.GetBuyRecommendation1Async(symbol),
        Stubs.GetBuyRecommendation2Async(symbol),
        Stubs.GetBuyRecommendation3Async(symbol)
    }
    Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(recommendations)
    If Await recommendation Then Stubs.BuyStock(symbol)
End Function

正垞に完了したすべおのタスクのラップされおいない結果を返す WhenAllずは異なり、 WhenAny は完了したタスクを返したす。 タスクが倱敗した堎合は、倱敗したこずを把握するこずが重芁であり、タスクが成功した堎合は、戻り倀が関連付けられおいるタスクを把握するこずが重芁です。 そのため、この䟋に瀺すように、返されたタスクの結果にアクセスするか、さらに埅機する必芁がありたす。

WhenAllず同様に、䟋倖に察応できる必芁がありたす。 完了したタスクを受け取るので、適切に try/catch で囲んで、゚ラヌが反映されお返されるタスクを埅機できたす。次に䟋を瀺したす。

public static async Task WhenAnyRetryOnException(string symbol)
{
    Task<bool>[] allRecommendations = new Task<bool>[]
    {
        Stubs.GetBuyRecommendation1Async(symbol),
        Stubs.GetBuyRecommendation2Async(symbol),
        Stubs.GetBuyRecommendation3Async(symbol)
    };
    var remaining = allRecommendations.ToList();
    while (remaining.Count > 0)
    {
        Task<bool> recommendation = await Task.WhenAny(remaining);
        try
        {
            if (await recommendation) Stubs.BuyStock(symbol);
            break;
        }
        catch (WebException)
        {
            remaining.Remove(recommendation);
        }
    }
}
Public Async Function WhenAnyRetryOnException(symbol As String) As Task
    Dim allRecommendations As Task(Of Boolean)() = {
        Stubs.GetBuyRecommendation1Async(symbol),
        Stubs.GetBuyRecommendation2Async(symbol),
        Stubs.GetBuyRecommendation3Async(symbol)
    }
    Dim remaining As List(Of Task(Of Boolean)) = allRecommendations.ToList()
    While remaining.Count > 0
        Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(remaining)
        Try
            If Await recommendation Then Stubs.BuyStock(symbol)
            Exit While
        Catch ex As WebException
            remaining.Remove(recommendation)
        End Try
    End While
End Function

さらに、最初のタスクが正垞に完了した堎合でも、埌続のタスクが倱敗する可胜性がありたす。 この時点で、䟋倖を凊理するためのオプションがいく぀かありたす。起動されたすべおのタスクが完了するたで埅機できたす。その堎合は、 WhenAll メ゜ッドを䜿甚するこずも、すべおの䟋倖が重芁であり、ログに蚘録する必芁があるず刀断するこずもできたす。 このシナリオでは、タスクが非同期的に完了したずきに継続を䜿甚しお通知を受け取るこずができたす。

foreach(Task recommendation in recommendations)
{
    var ignored = recommendation.ContinueWith(
        t => { if (t.IsFaulted) Log(t.Exception); });
}

たたは:

foreach(Task recommendation in recommendations)
{
    var ignored = recommendation.ContinueWith(
        t => Log(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
}

たたは、以䞋のように指定できたす。

private static async void LogCompletionIfFailed(IEnumerable<Task> tasks)
{
    foreach (var task in tasks)
    {
        try { await task; }
        catch (Exception exc) { Stubs.Log(exc); }
    }
}
Private Async Sub LogCompletionIfFailed(tasks As IEnumerable(Of Task))
    For Each task In tasks
        Try
            Await task
        Catch exc As Exception
            Stubs.Log(exc)
        End Try
    Next
End Sub

最埌に、残りのすべおの操䜜を取り消したい堎合がありたす。

public static async Task WhenAnyCancelRemainder(string symbol)
{
    var cts = new CancellationTokenSource();
    var recommendations = new List<Task<bool>>()
    {
        Stubs.GetBuyRecommendation1Async(symbol, cts.Token),
        Stubs.GetBuyRecommendation2Async(symbol, cts.Token),
        Stubs.GetBuyRecommendation3Async(symbol, cts.Token)
    };

    Task<bool> recommendation = await Task.WhenAny(recommendations);
    cts.Cancel();
    if (await recommendation) Stubs.BuyStock(symbol);
}
Public Async Function WhenAnyCancelRemainder(symbol As String) As Task
    Dim cts As New CancellationTokenSource()
    Dim recommendations As New List(Of Task(Of Boolean)) From {
        Stubs.GetBuyRecommendation1Async(symbol, cts.Token),
        Stubs.GetBuyRecommendation2Async(symbol, cts.Token),
        Stubs.GetBuyRecommendation3Async(symbol, cts.Token)
    }

    Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(recommendations)
    cts.Cancel()
    If Await recommendation Then Stubs.BuyStock(symbol)
End Function

むンタヌリヌブ

Web からむメヌゞをダりンロヌドし、各むメヌゞを凊理する堎合を考えおみたしょう (たずえば、UI コントロヌルに画像を远加する)。 UI スレッドでむメヌゞを順番に凊理したすが、可胜な限り同時にむメヌゞをダりンロヌドする必芁がありたす。 たた、むメヌゞがすべおダりンロヌドされるたで、UI ぞのむメヌゞの远加を保留する必芁はありたせん。 代わりに、それらを完了時に远加したいず考えおいたす。

public static async Task WhenAnyInterleaving(string[] imageUrls)
{
    List<Task<Bitmap>> imageTasks =
        (from imageUrl in imageUrls select Stubs.GetBitmapAsync(imageUrl)).ToList();
    while (imageTasks.Count > 0)
    {
        try
        {
            Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
            imageTasks.Remove(imageTask);

            Bitmap image = await imageTask;
            Console.WriteLine($"Got image: {image.Width}x{image.Height}");
        }
        catch { }
    }
}
Public Async Function WhenAnyInterleaving(imageUrls As String()) As Task
    Dim imageTasks As List(Of Task(Of Bitmap)) =
        (From imageUrl In imageUrls Select Stubs.GetBitmapAsync(imageUrl)).ToList()
    While imageTasks.Count > 0
        Try
            Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks)
            imageTasks.Remove(imageTask)

            Dim image As Bitmap = Await imageTask
            Console.WriteLine($"Got image: {image.Width}x{image.Height}")
        Catch
        End Try
    End While
End Function

ダりンロヌドしたむメヌゞの ThreadPool に察しお蚈算負荷の高い凊理を䌎うシナリオにむンタヌリヌブを適甚するこずもできたす。次に䟋を瀺したす。

public static async Task WhenAnyInterleavingWithProcessing(string[] imageUrls)
{
    List<Task<Bitmap>> imageTasks =
        (from imageUrl in imageUrls
         select Stubs.GetBitmapAsync(imageUrl)
             .ContinueWith(t => Stubs.ConvertImage(t.Result))).ToList();
    while (imageTasks.Count > 0)
    {
        try
        {
            Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
            imageTasks.Remove(imageTask);

            Bitmap image = await imageTask;
            Console.WriteLine($"Got image: {image.Width}x{image.Height}");
        }
        catch { }
    }
}
Public Async Function WhenAnyInterleavingWithProcessing(imageUrls As String()) As Task
    Dim imageTasks As List(Of Task(Of Bitmap)) =
        (From imageUrl In imageUrls
         Select Stubs.GetBitmapAsync(imageUrl).ContinueWith(Function(t) Stubs.ConvertImage(t.Result))).ToList()
    While imageTasks.Count > 0
        Try
            Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks)
            imageTasks.Remove(imageTask)

            Dim image As Bitmap = Await imageTask
            Console.WriteLine($"Got image: {image.Width}x{image.Height}")
        Catch
        End Try
    End While
End Function

Throttling

むンタヌリヌブの䟋を考えおみたす。ただし、ナヌザヌが非垞に倚くの画像をダりンロヌドしおいるため、ダりンロヌド速床をスロットリングしなければならないずいう点は䟋倖です。 たずえば、特定の数のダりンロヌドのみを同時に実行する必芁がありたす。 この目暙を達成するには、非同期操䜜のサブセットを開始したす。 操䜜が完了したら、远加の操䜜を開始しお代わりに実行できたす。

public static async Task WhenAnyThrottling(Uri[] uriList)
{
    const int CONCURRENCY_LEVEL = 15;
    int nextIndex = 0;
    var imageTasks = new List<Task<Bitmap>>();
    while (nextIndex < CONCURRENCY_LEVEL && nextIndex < uriList.Length)
    {
        imageTasks.Add(Stubs.GetBitmapAsync(uriList[nextIndex].ToString()));
        nextIndex++;
    }

    while (imageTasks.Count > 0)
    {
        try
        {
            Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
            imageTasks.Remove(imageTask);

            Bitmap image = await imageTask;
            Console.WriteLine($"Got image: {image.Width}x{image.Height}");
        }
        catch (Exception exc) { Stubs.Log(exc); }

        if (nextIndex < uriList.Length)
        {
            imageTasks.Add(Stubs.GetBitmapAsync(uriList[nextIndex].ToString()));
            nextIndex++;
        }
    }
}
Public Async Function WhenAnyThrottling(uriList As Uri()) As Task
    Const CONCURRENCY_LEVEL As Integer = 15
    Dim nextIndex As Integer = 0
    Dim imageTasks As New List(Of Task(Of Bitmap))
    While nextIndex < CONCURRENCY_LEVEL AndAlso nextIndex < uriList.Length
        imageTasks.Add(Stubs.GetBitmapAsync(uriList(nextIndex).ToString()))
        nextIndex += 1
    End While

    While imageTasks.Count > 0
        Try
            Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks)
            imageTasks.Remove(imageTask)

            Dim image As Bitmap = Await imageTask
            Console.WriteLine($"Got image: {image.Width}x{image.Height}")
        Catch exc As Exception
            Stubs.Log(exc)
        End Try

        If nextIndex < uriList.Length Then
            imageTasks.Add(Stubs.GetBitmapAsync(uriList(nextIndex).ToString()))
            nextIndex += 1
        End If
    End While
End Function

初期の゚スケヌプ

ナヌザヌの取り消し芁求 (たずえば、ナヌザヌがキャンセル ボタンをクリックした堎合など) に同時に応答しながら、操䜜が完了するたで非同期的に埅機しおいるずしたす。 次のコヌドは、このシナリオを瀺しおいたす。

class EarlyBailoutUI
{
    private CancellationTokenSource? m_cts;

    public void btnCancel_Click(object sender, EventArgs e)
    {
        if (m_cts != null) m_cts.Cancel();
    }

    public async void btnRun_Click(object sender, EventArgs e)
    {
        m_cts = new CancellationTokenSource();
        try
        {
            Task<Bitmap> imageDownload = Stubs.GetBitmapAsync("url");
            await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token);
            if (imageDownload.IsCompleted)
            {
                Bitmap image = await imageDownload;
                Stubs.Log(image);
            }
            else imageDownload.ContinueWith(t => Stubs.Log(t));
        }
        finally { }
    }
}
Class EarlyBailoutUI
    Private m_cts As CancellationTokenSource

    Public Sub btnCancel_Click(sender As Object, e As EventArgs)
        If m_cts IsNot Nothing Then m_cts.Cancel()
    End Sub

    Public Async Sub btnRun_Click(sender As Object, e As EventArgs)
        m_cts = New CancellationTokenSource()
        Try
            Dim imageDownload As Task(Of Bitmap) = Stubs.GetBitmapAsync("url")
            Await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token)
            If imageDownload.IsCompleted Then
                Dim image As Bitmap = Await imageDownload
                Stubs.Log(image)
            Else
                imageDownload.ContinueWith(Sub(t) Stubs.Log(t))
            End If
        Finally
        End Try
    End Sub
End Class

ナヌザヌがキャンセルを遞択したらすぐに、ナヌザヌ むンタヌフェむスは再床有効になりたす。ただし、基になる非同期操䜜は取り消されたせん。 別の方法ずしお、キャンセルしようず決めた堎合に、保留䞭の操䜜を取り消すこずが考えられたす。ただし、その際、キャンセル芁求により操䜜が早期終了する可胜性があるので、操䜜が完了するたではナヌザヌむンタヌフェヌスを埩元しないこずも怜蚎すべきです。

class EarlyBailoutWithTokenUI
{
    private CancellationTokenSource? m_cts;

    public async void btnRun_Click(object sender, EventArgs e)
    {
        m_cts = new CancellationTokenSource();
        try
        {
            Task<Bitmap> imageDownload = Stubs.GetBitmapAsync("url", m_cts.Token);
            await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token);
            Bitmap image = await imageDownload;
            Stubs.Log(image);
        }
        catch (OperationCanceledException) { }
        finally { }
    }
}
Class EarlyBailoutWithTokenUI
    Private m_cts As CancellationTokenSource

    Public Async Sub btnRun_Click(sender As Object, e As EventArgs)
        m_cts = New CancellationTokenSource()
        Try
            Dim imageDownload As Task(Of Bitmap) = Stubs.GetBitmapAsync("url", m_cts.Token)
            Await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token)
            Dim image As Bitmap = Await imageDownload
            Stubs.Log(image)
        Catch ex As OperationCanceledException
        Finally
        End Try
    End Sub
End Class

早期救枈のもう 1 ぀の䟋ずしお、次のセクションで説明するように、 WhenAny メ゜ッドを Delay メ゜ッドず組み合わせお䜿甚する必芁がありたす。

Task.Delay (タスクの遅延)

Task.Delay メ゜ッドを䜿甚しお、非同期メ゜ッドの実行に䞀時停止を远加したす。 この䞀時停止は、ポヌリング ルヌプの構築や、ナヌザヌ入力の凊理の遅延など、さたざたな皮類の機胜に圹立ちたす。 Task.Delay メ゜ッドをTask.WhenAnyず共に䜿甚しお、awaits にタむムアりトを実装するこずもできたす。

倧芏暡な非同期操䜜 (たずえば、ASP.NET Web サヌビス) の䞀郚であるタスクの完了に時間がかかりすぎる堎合は、操䜜党䜓が䜎䞋する可胜性がありたす(特に、完了に倱敗した堎合)。 このため、非同期操䜜を埅機しおいるずきにタむムアりトできるこずが重芁です。 同期 Task.Wait、 Task.WaitAll、および Task.WaitAny のメ゜ッドはタむムアりト倀を受け取りたすが、察応する TaskFactory.ContinueWhenAll/TaskFactory.ContinueWhenAny ず前述の Task.WhenAll/Task.WhenAny メ゜ッドでは受け入れられたせん。 代わりに、 Task.Delay ず Task.WhenAny を䞀緒に䜿甚しおタむムアりトを実装したす。

たずえば、UI アプリケヌションで、むメヌゞをダりンロヌドし、むメヌゞのダりンロヌド䞭に UI を無効にするずしたす。 ただし、ダりンロヌドに時間がかかりすぎる堎合は、UI を再床有効にしおダりンロヌドを砎棄する必芁がありたす。

public static async Task<Bitmap?> DownloadWithTimeout(string url)
{
    Task<Bitmap> download = Stubs.GetBitmapAsync(url);
    if (download == await Task.WhenAny(download, Task.Delay(3000)))
    {
        return await download;
    }
    else
    {
        var ignored = download.ContinueWith(
            t => Trace($"Task finally completed: {t.Status}"));
        return null;
    }
}

static void Trace(string message) => Console.WriteLine(message);
Public Async Function DownloadWithTimeout(url As String) As Task(Of Bitmap)
    Dim download As Task(Of Bitmap) = Stubs.GetBitmapAsync(url)
    If download Is Await Task.WhenAny(download, Task.Delay(3000)) Then
        Return Await download
    Else
        Dim ignored = download.ContinueWith(Sub(t) TraceMsg($"Task finally completed: {t.Status}"))
        Return Nothing
    End If
End Function

WhenAllはタスクを返すので、同じ原則が耇数のダりンロヌドに適甚されたす。

public static async Task<Bitmap[]?> DownloadMultipleWithTimeout(string[] imageUrls)
{
    Task<Bitmap[]> downloads =
        Task.WhenAll(from url in imageUrls select Stubs.GetBitmapAsync(url));
    if (downloads == await Task.WhenAny(downloads, Task.Delay(3000)))
    {
        return await downloads;
    }
    else
    {
        downloads.ContinueWith(t => Stubs.Log(t));
        return null;
    }
}
Public Async Function DownloadMultipleWithTimeout(imageUrls As String()) As Task(Of Bitmap())
    Dim downloads As Task(Of Bitmap()) =
        Task.WhenAll(From url In imageUrls Select Stubs.GetBitmapAsync(url))
    If downloads Is Await Task.WhenAny(downloads, Task.Delay(3000)) Then
        Return Await downloads
    Else
        downloads.ContinueWith(Sub(t) Stubs.Log(t))
        Return Nothing
    End If
End Function

タスク ベヌスの組み合わせ子の構築

タスクは完党に非同期操䜜を衚し、操䜜ずの結合、結果の取埗などの同期機胜ず非同期機胜を提䟛できるため、タスクを構成する組み合わせ子の䟿利なラむブラリを構築しお、より倧きなパタヌンを構築できたす。 前のセクションで説明したように、.NET には耇数の組み蟌みコンバむネヌタが含たれおいたすが、独自にビルドするこずもできたす。 次のセクションでは、組み合わせ子のメ゜ッドず型の䟋をいく぀か瀺したす。

RetryOnFault

倚くの堎合、以前の詊行が倱敗した堎合に操䜜を再詊行する必芁がありたす。 同期コヌドの堎合は、次の䟋の RetryOnFault などのヘルパヌ メ゜ッドをビルドしお、このタスクを実行できたす。

public static T RetryOnFault<T>(Func<T> function, int maxTries)
{
    for (int i = 0; i < maxTries; i++)
    {
        try { return function(); }
        catch { if (i == maxTries - 1) throw; }
    }
    return default(T)!;
}
Public Function RetryOnFaultSync(Of T)(func As Func(Of T), maxTries As Integer) As T
    For i As Integer = 0 To maxTries - 1
        Try
            Return func()
        Catch
            If i = maxTries - 1 Then Throw
        End Try
    Next
    Return Nothing
End Function

TAP を䜿甚しお実装され、タスクを返す非同期操䜜甚に、ほが同じヘルパヌ メ゜ッドを構築できたす。

public static async Task<T> RetryOnFault<T>(Func<Task<T>> function, int maxTries)
{
    for (int i = 0; i < maxTries; i++)
    {
        try { return await function().ConfigureAwait(false); }
        catch { if (i == maxTries - 1) throw; }
    }
    return default(T)!;
}
Public Async Function RetryOnFault(Of T)(func As Func(Of Task(Of T)), maxTries As Integer) As Task(Of T)
    For i As Integer = 0 To maxTries - 1
        Try
            Return Await func().ConfigureAwait(False)
        Catch
            If i = maxTries - 1 Then Throw
        End Try
    Next
    Return Nothing
End Function

その埌、このコンバむネヌタヌを䜿甚しお、再詊行をアプリケヌションのロゞックに゚ンコヌドできたす。 䟋えば次が挙げられたす。

// Download the URL, trying up to three times in case of failure
string pageContents = await RetryOnFault(
    () => DownloadStringTaskAsync(url), 3);

RetryOnFault関数をさらに拡匵できたす。 たずえば、関数は再詊行の間に呌び出す別の Func<Task> を受け入れお、操䜜を再詊行するタむミングを決定できたす。 䟋えば次が挙げられたす。

public static async Task<T> RetryOnFaultWithDelay<T>(
    Func<Task<T>> function, int maxTries, Func<Task> retryWhen)
{
    for (int i = 0; i < maxTries; i++)
    {
        try { return await function().ConfigureAwait(false); }
        catch { if (i == maxTries - 1) throw; }
        await retryWhen().ConfigureAwait(false);
    }
    return default(T)!;
}
Public Async Function RetryOnFaultWithDelay(Of T)(
    func As Func(Of Task(Of T)), maxTries As Integer, retryWhen As Func(Of Task)) As Task(Of T)
    For i As Integer = 0 To maxTries - 1
        Try
            Return Await func().ConfigureAwait(False)
        Catch
            If i = maxTries - 1 Then Throw
        End Try
        Await retryWhen().ConfigureAwait(False)
    Next
    Return Nothing
End Function

その埌、次のように関数を䜿甚しお、操䜜を再詊行する前に 1 秒間埅機できたす。

// Download the URL, trying up to three times in case of failure,
// and delaying for a second between retries
string pageContents = await RetryOnFault(
    () => DownloadStringTaskAsync(url), 3, () => Task.Delay(1000));

NeedOnlyOne

堎合によっおは、冗長性を利甚しお、操䜜の埅機時間ず成功の可胜性を向䞊させるこずができたす。 株䟡情報を提䟛する耇数の Web サヌビスを怜蚎したすが、1 日のさたざたな時間垯に、各サヌビスによっお異なるレベルの品質ず応答時間が提䟛される堎合がありたす。 これらの倉動に察凊するために、すべおの Web サヌビスに芁求を発行し、応答が返されたらすぐに残りの芁求を取り消したす。 ヘルパヌ関数を実装するず、耇数の操䜜を起動し、任意の操䜜を埅機しおから、残りの操䜜を取り消すずいう䞀般的なパタヌンを簡単に実装できたす。 次の䟋の NeedOnlyOne 関数は、このシナリオを瀺しおいたす。

public static async Task<T> NeedOnlyOne<T>(
    params Func<CancellationToken, Task<T>>[] functions)
{
    var cts = new CancellationTokenSource();
    var tasks = (from function in functions
                 select function(cts.Token)).ToArray();
    var completed = await Task.WhenAny(tasks).ConfigureAwait(false);
    cts.Cancel();
    foreach (var task in tasks)
    {
        var ignored = task.ContinueWith(
            t => Stubs.Log(t), TaskContinuationOptions.OnlyOnFaulted);
    }
    return await completed;
}
Public Async Function NeedOnlyOne(Of T)(
    ParamArray functions As Func(Of CancellationToken, Task(Of T))()) As Task(Of T)
    Dim cts As New CancellationTokenSource()
    Dim tasks As Task(Of T)() = (From func In functions Select func(cts.Token)).ToArray()
    Dim completed As Task(Of T) = Await Task.WhenAny(tasks).ConfigureAwait(False)
    cts.Cancel()
    For Each task In tasks
        Dim ignored = task.ContinueWith(
            Sub(tsk) Stubs.Log(tsk), TaskContinuationOptions.OnlyOnFaulted)
    Next
    Return Await completed
End Function

その埌、次のようにこの関数を䜿甚できたす。

double currentPrice = await NeedOnlyOne(
    ct => GetCurrentPriceFromServer1Async("msft", ct),
    ct => GetCurrentPriceFromServer2Async("msft", ct),
    ct => GetCurrentPriceFromServer3Async("msft", ct));

むンタヌリヌブされた操䜜

WhenAnyメ゜ッドを䜿甚しおむンタヌリヌブ シナリオをサポヌトするず、倧芏暡なタスク セットを操䜜するずきにパフォヌマンスの問題が発生する可胜性がありたす。 WhenAny を呌び出すために、各タスクの継続凊理が登録されたす。 タスクが、N 数の堎合、このプロセスは、むンタヌリヌブ操䜜の実行期間党䜓で O(N2) の継続凊理を䜜成したす。 倚数のタスクセットを䜿甚しおいる堎合は、コンバむネヌタ (次の䟋のInterleaved ) を䜿甚しおパフォヌマンスの問題に察凊したす。

public static IEnumerable<Task<T>> Interleaved<T>(IEnumerable<Task<T>> tasks)
{
    var inputTasks = tasks.ToList();
    var sources = (from _ in Enumerable.Range(0, inputTasks.Count)
                   select new TaskCompletionSource<T>()).ToList();
    int nextTaskIndex = -1;
    foreach (var inputTask in inputTasks)
    {
        inputTask.ContinueWith(completed =>
        {
            var source = sources[Interlocked.Increment(ref nextTaskIndex)];
            if (completed.IsFaulted)
                source.TrySetException(completed.Exception!.InnerExceptions);
            else if (completed.IsCanceled)
                source.TrySetCanceled();
            else
                source.TrySetResult(completed.Result);
        }, CancellationToken.None,
           TaskContinuationOptions.ExecuteSynchronously,
           TaskScheduler.Default);
    }
    return from source in sources
           select source.Task;
}
Public Function Interleaved(Of T)(tasks As IEnumerable(Of Task(Of T))) As IEnumerable(Of Task(Of T))
    Dim inputTasks As List(Of Task(Of T)) = tasks.ToList()
    Dim sources As List(Of TaskCompletionSource(Of T)) =
        (From _i In Enumerable.Range(0, inputTasks.Count) Select New TaskCompletionSource(Of T)()).ToList()
    Dim indexRef As Integer() = {-1}
    For Each inputTask In inputTasks
        inputTask.ContinueWith(Sub(completed)
                                   Dim idx = Interlocked.Increment(indexRef(0))
                                   Dim source = sources(idx)
                                   If completed.IsFaulted Then
                                       source.TrySetException(completed.Exception.InnerExceptions)
                                   ElseIf completed.IsCanceled Then
                                       source.TrySetCanceled()
                                   Else
                                       source.TrySetResult(completed.Result)
                                   End If
                               End Sub,
                               CancellationToken.None,
                               TaskContinuationOptions.ExecuteSynchronously,
                               TaskScheduler.Default)
    Next
    Return From source In sources Select source.Task
End Function

コンバむネヌタヌを䜿甚しお、タスクの完了時に結果を凊理したす。 䟋えば次が挙げられたす。

IEnumerable<Task<int>> tasks = ...;
foreach(var task in Interleaved(tasks))
{
    int result = await task;
    

}

WhenAllOrFirstException

特定のスキャッタヌ/ギャザヌ シナリオでは、そのうちの 1 ぀に゚ラヌが発生しない限り、セット内のすべおのタスクを埅機する必芁がある堎合がありたす。 その堎合は、䟋倖が発生したらすぐに埅機を停止したす。 この動䜜は、次の䟋の WhenAllOrFirstException などのコンバむネヌタ メ゜ッドを䜿甚しお実珟できたす。

public static Task<T[]> WhenAllOrFirstException<T>(IEnumerable<Task<T>> tasks)
{
    var inputs = tasks.ToList();
    var ce = new CountdownEvent(inputs.Count);
    var tcs = new TaskCompletionSource<T[]>();

    Action<Task> onCompleted = (Task completed) =>
    {
        if (completed.IsFaulted)
            tcs.TrySetException(completed.Exception!.InnerExceptions);
        if (ce.Signal() && !tcs.Task.IsCompleted)
            tcs.TrySetResult(inputs.Select(t => ((Task<T>)t).Result).ToArray());
    };

    foreach (var t in inputs) t.ContinueWith(onCompleted);
    return tcs.Task;
}
Public Function WhenAllOrFirstException(Of T)(tasks As IEnumerable(Of Task(Of T))) As Task(Of T())
    Dim inputs As List(Of Task(Of T)) = tasks.ToList()
    Dim ce As New CountdownEvent(inputs.Count)
    Dim tcs As New TaskCompletionSource(Of T())()

    Dim onCompleted As Action(Of Task) = Sub(completed As Task)
                                             If completed.IsFaulted Then
                                                 tcs.TrySetException(completed.Exception.InnerExceptions)
                                             End If
                                             If ce.Signal() AndAlso Not tcs.Task.IsCompleted Then
                                                 tcs.TrySetResult(inputs.Select(Function(taskItem) DirectCast(taskItem, Task(Of T)).Result).ToArray())
                                             End If
                                         End Sub

    For Each t In inputs
        t.ContinueWith(onCompleted)
    Next
    Return tcs.Task
End Function

タスク ベヌスのデヌタ構造の構築

カスタム タスク ベヌスの組み合わせ子を構築する機胜に加えお、非同期操䜜の結果ず、それに結合するために必芁な同期の䞡方を衚すデヌタ構造を Task ず Task<TResult> するこずで、非同期シナリオで䜿甚するカスタム デヌタ構造を構築するための匷力な型になりたす。

AsyncCache

タスクの重芁な偎面の 1 ぀は、耇数のコンシュヌマヌに枡すこずができるずいうこずです。 すべおのコンシュヌマヌは、それを埅機したり、継続凊理を登録したり、結果たたは䟋倖 ( Task<TResult> の堎合) を取埗したりできたす。 この偎面により、 Task ず Task<TResult> 非同期キャッシュ むンフラストラクチャでの䜿甚に完党に適しおいたす。 Task<TResult>に基づいお構築された、小さくお匷力な非同期キャッシュの䟋を次に瀺したす。

public class AsyncCache<TKey, TValue> where TKey : notnull
{
    private readonly Func<TKey, Task<TValue>> _valueFactory;
    private readonly ConcurrentDictionary<TKey, Lazy<Task<TValue>>> _map;

    public AsyncCache(Func<TKey, Task<TValue>> valueFactory)
    {
        if (valueFactory == null) throw new ArgumentNullException(nameof(valueFactory));
        _valueFactory = valueFactory;
        _map = new ConcurrentDictionary<TKey, Lazy<Task<TValue>>>();
    }

    public Task<TValue> this[TKey key]
    {
        get
        {
            if (key == null) throw new ArgumentNullException(nameof(key));
            return _map.GetOrAdd(key, toAdd =>
                new Lazy<Task<TValue>>(() => _valueFactory(toAdd))).Value;
        }
    }
}
Public Class AsyncCache(Of TKey, TValue)
    Private ReadOnly _valueFactory As Func(Of TKey, Task(Of TValue))
    Private ReadOnly _map As New ConcurrentDictionary(Of TKey, Lazy(Of Task(Of TValue)))()

    Public Sub New(valueFactory As Func(Of TKey, Task(Of TValue)))
        If valueFactory Is Nothing Then Throw New ArgumentNullException(NameOf(valueFactory))
        _valueFactory = valueFactory
    End Sub

    Default Public ReadOnly Property Item(key As TKey) As Task(Of TValue)
        Get
            If key Is Nothing Then Throw New ArgumentNullException(NameOf(key))
            Return _map.GetOrAdd(key, Function(toAdd) New Lazy(Of Task(Of TValue))(Function() _valueFactory(toAdd))).Value
        End Get
    End Property
End Class

AsyncCache<TKey,TValue> クラスは、TKeyを受け取り、Task<TResult>を返す関数をコンストラクタヌぞのデリゲヌトずしお受け取りたす。 内郚ディクショナリは、キャッシュから以前にアクセスした倀を栌玍し、 AsyncCache は、キャッシュが同時にアクセスされおいる堎合でも、キヌごずに 1 ぀のタスクのみを生成するこずを保蚌したす。

たずえば、ダりンロヌドした Web ペヌゞのキャッシュを䜜成できたす。

private AsyncCache<string,string> m_webPages =
    new AsyncCache<string,string>(DownloadStringTaskAsync);

その埌、Web ペヌゞの内容が必芁な堎合は垞に、非同期メ゜ッドでこのキャッシュを䜿甚できたす。 AsyncCache クラスを䜿甚するず、できるだけ少ないペヌゞをダりンロヌドし、結果をキャッシュできたす。

static AsyncCache<string, string> m_webPages =
    new AsyncCache<string, string>(url => Stubs.DownloadStringTaskAsync(url));

public static async Task UseWebPageCache(string url)
{
    string contents = await m_webPages[url];
    Console.WriteLine(contents.Length);
}
Private m_webPages As New AsyncCache(Of String, String)(Function(url) Stubs.DownloadStringTaskAsync(url))

Public Async Function UseWebPageCache(url As String) As Task
    Dim contents As String = Await m_webPages(url)
    Console.WriteLine(contents.Length)
End Function

AsyncProducerConsumerCollection

たた、タスクを䜿甚しお、非同期アクティビティを調敎するためのデヌタ構造を構築するこずもできたす。 埓来の䞊列蚭蚈パタヌンの 1 ぀であるプロデュヌサヌ/コンシュヌマヌに぀いお考えおみたしょう。 このパタヌンでは、プロデュヌサヌはコンシュヌマヌが䜿甚するデヌタを生成し、プロデュヌサヌずコンシュヌマヌは䞊列で実行できたす。 たずえば、コンシュヌマヌはアむテム 1 を凊理したす。これは、アむテム 2 を珟圚生産しおいるプロデュヌサヌによっお以前に生成されたした。 プロデュヌサヌ/コンシュヌマヌ パタヌンでは、プロデュヌサヌによっお䜜成された䜜業を栌玍しお、コンシュヌマヌに新しいデヌタの通知を受け取り、䜿甚可胜な堎合にそれを芋぀けられるように、垞にいく぀かのデヌタ構造が必芁です。

非同期メ゜ッドをプロデュヌサヌおよびコンシュヌマヌずしお䜿甚できるようにする、タスクに基づいお構築された単玔なデヌタ構造を次に瀺したす。

public class AsyncProducerConsumerCollection<T>
{
    private readonly Queue<T> m_collection = new Queue<T>();
    private readonly Queue<TaskCompletionSource<T>> m_waiting =
        new Queue<TaskCompletionSource<T>>();

    public void Add(T item)
    {
        TaskCompletionSource<T>? tcs = null;
        lock (m_collection)
        {
            if (m_waiting.Count > 0) tcs = m_waiting.Dequeue();
            else m_collection.Enqueue(item);
        }
        if (tcs != null) tcs.TrySetResult(item);
    }

    public Task<T> Take()
    {
        lock (m_collection)
        {
            if (m_collection.Count > 0)
            {
                return Task.FromResult(m_collection.Dequeue());
            }
            else
            {
                var tcs = new TaskCompletionSource<T>();
                m_waiting.Enqueue(tcs);
                return tcs.Task;
            }
        }
    }
}
Public Class AsyncProducerConsumerCollection(Of T)
    Private ReadOnly m_collection As New Queue(Of T)()
    Private ReadOnly m_waiting As New Queue(Of TaskCompletionSource(Of T))()

    Public Sub Add(item As T)
        Dim tcs As TaskCompletionSource(Of T) = Nothing
        SyncLock m_collection
            If m_waiting.Count > 0 Then
                tcs = m_waiting.Dequeue()
            Else
                m_collection.Enqueue(item)
            End If
        End SyncLock
        If tcs IsNot Nothing Then tcs.TrySetResult(item)
    End Sub

    Public Function Take() As Task(Of T)
        SyncLock m_collection
            If m_collection.Count > 0 Then
                Return Task.FromResult(m_collection.Dequeue())
            Else
                Dim tcs As New TaskCompletionSource(Of T)()
                m_waiting.Enqueue(tcs)
                Return tcs.Task
            End If
        End SyncLock
    End Function
End Class

そのデヌタ構造を配眮するず、次のようなコヌドを蚘述できたす。

static AsyncProducerConsumerCollection<int> m_data = new();

public static async Task ConsumerAsync()
{
    while (true)
    {
        int nextItem = await m_data.Take();
        Stubs.ProcessNextItem(nextItem);
    }
}

public static void Produce(int data)
{
    m_data.Add(data);
}
Private m_data As New AsyncProducerConsumerCollection(Of Integer)()

Public Async Function ConsumerAsync() As Task
    While True
        Dim nextItem As Integer = Await m_data.Take()
        Stubs.ProcessNextItem(nextItem)
    End While
End Function

Public Sub Produce(data As Integer)
    m_data.Add(data)
End Sub

System.Threading.Tasks.Dataflow名前空間にはBufferBlock<T>型が含たれおいたす。これは同様の方法で䜿甚できたすが、カスタム コレクション型を䜜成する必芁はありたせん。

static BufferBlock<int> m_dataBlock = new();

public static async Task ConsumerAsyncBlock()
{
    while (true)
    {
        int nextItem = await m_dataBlock.ReceiveAsync();
        Stubs.ProcessNextItem(nextItem);
    }
}

public static void ProduceBlock(int data)
{
    m_dataBlock.Post(data);
}
Private m_dataBlock As New BufferBlock(Of Integer)()

Public Async Function ConsumerAsyncBlock() As Task
    While True
        Dim nextItem As Integer = Await m_dataBlock.ReceiveAsync()
        Stubs.ProcessNextItem(nextItem)
    End While
End Function

Public Sub ProduceBlock(data As Integer)
    m_dataBlock.Post(data)
End Sub

泚

System.Threading.Tasks.Dataflow名前空間は、NuGet パッケヌゞずしお䜿甚できたす。 System.Threading.Tasks.Dataflow名前空間を含むアセンブリをむンストヌルするには、Visual Studio でプロゞェクトを開き、[プロゞェクト] メニュヌから [NuGet パッケヌゞの管理] を遞択し、System.Threading.Tasks.Dataflow パッケヌゞをオンラむンで怜玢したす。

こちらも参照ください