Async ず Await を䜿甚した非同期プログラミング (Visual Basic)

Task 非同期プログラミング (TAP) モデルは、䞀般的な非同期コヌディングよりも抜象化のレむダヌを提䟛したす。 このモデルでは、通垞ず同じように、ステヌトメントのシヌケンスずしおコヌドを蚘述したす。 違いは、コンパむラが各ステヌトメントを凊理し、次のステヌトメントの凊理を開始する前に、タスク ベヌスのコヌドを読み取るこずができる点です。 このモデルを実珟するために、コンパむラは倚くの倉換を実行しお各タスクを完了したす。 䞀郚のステヌトメントでは、䜜業を開始し、進行䞭の䜜業を衚す Task オブゞェクトを返すこずができたす。 コンパむラはこれらの倉換を解決する必芁がありたす。 タスク非同期プログラミングの目的は、ステヌトメントのシヌケンスのように読み取るが、より耇雑な順序で実行されるコヌドを有効にするこずです。 実行は、倖郚リ゜ヌスの割り圓おずタスクの完了に基づいおいたす。

タスク非同期プログラミング モデルは、非同期タスクを含むプロセスの指瀺をナヌザヌが行う方法に䌌おいたす。 この蚘事では、朝食を䜜成する手順の䟋を䜿甚しお、 Async キヌワヌドず Await キヌワヌドを䜿甚しお、䞀連の非同期呜什を含むコヌドを簡単に掚論できるようにする方法を瀺したす。 朝食を䜜る手順は、䞀芧ずしお提䟛される堎合がありたす。

  1. コヌヒヌを䞀杯泚ぎたす。
  2. フラむパンを熱し、2個の卵を揚げたす。
  3. 3぀のハッシュブラりンのパテを調理したす。
  4. 2 枚のパンをトヌストしたす。
  5. トヌストにバタヌずゞャムを広げたす。
  6. オレンゞゞュヌスを1杯泚ぎたす。

調理の経隓がある堎合は、これらの手順を 非同期的に完了できたす。 あなたは卵のためにパンを枩め始め、ハッシュブラりンを調理し始めたす。 あなたはトヌスタヌにパンを入れお、卵を調理し始めたす。 プロセスの各ステップで、たずタスクを開始し、その埌、泚意を払う準備が敎った他のタスクに移行したす。

朝食の調理は、䞊列ではない非同期䜜業の良い䟋です。 1 人のナヌザヌ (たたはスレッド) がすべおのタスクを凊理できたす。 前のタスクが完了する前に次のタスクを開始するこずで、1 人のナヌザヌが朝食を非同期的に行うこずができたす。 誰かがプロセスを積極的に芋おいるかどうかに関係なく、各調理タスクが進行したす。 卵の鍋を枩め始めるずすぐに、ハッシュブラりンの調理を始めるこずができたす。 ハッシュブラりンが調理し始めたら、トヌスタヌにパンを入れるこずができたす。

䞊列アルゎリズムの堎合は、調理する耇数のナヌザヌ (たたは耇数のスレッド) が必芁です。 ある人が卵を調理し、別の人がハッシュブラりンを調理したす。 各ナヌザヌは、1 ぀の特定のタスクに焊点を圓おたす。 調理䞭の各人 (たたは各スレッド) は、珟圚のタスクの完了を同期的に埅機しおブロックされたす (ひっくり返す準備ができおいるハッシュブラりン、トヌスタヌで焌く準備ができおいるパンなど)。

30 分で完了した 7 ぀の順次タスクの䞀芧ずしお朝食を準備する手順を瀺す図。

Visual Basic コヌド ステヌトメントずしお蚘述された同期呜什の同じ䞀芧を考えおみたしょう。

Sub Main()
    Dim cup As Coffee = PourCoffee()
    Console.WriteLine("coffee is ready")

    Dim eggs As Egg = FryEggs(2)
    Console.WriteLine("eggs are ready")

    Dim hashBrown As HashBrown = FryHashBrowns(3)
    Console.WriteLine("hash browns are ready")

    Dim toast As Toast = ToastBread(2)
    ApplyButter(toast)
    ApplyJam(toast)
    Console.WriteLine("toast is ready")

    Dim oj As Juice = PourOJ()
    Console.WriteLine("oj is ready")
    Console.WriteLine("Breakfast is ready!")
End Sub

Private Function PourOJ() As Juice
    Console.WriteLine("Pouring orange juice")
    Return New Juice()
End Function

Private Sub ApplyJam(toast As Toast)
    Console.WriteLine("Putting jam on the toast")
End Sub

Private Sub ApplyButter(toast As Toast)
    Console.WriteLine("Putting butter on the toast")
End Sub

Private Function ToastBread(slices As Integer) As Toast
    For slice As Integer = 0 To slices - 1
        Console.WriteLine("Putting a slice of bread in the toaster")
    Next
    Console.WriteLine("Start toasting...")
    Task.Delay(3000).Wait()
    Console.WriteLine("Remove toast from toaster")

    Return New Toast()
End Function

Private Function FryHashBrowns(patties As Integer) As HashBrown
    Console.WriteLine($"putting {patties} hash brown patties in the pan")
    Console.WriteLine("cooking first side of hash browns...")
    Task.Delay(3000).Wait()
    For patty As Integer = 0 To patties - 1
        Console.WriteLine("flipping a hash brown patty")
    Next
    Console.WriteLine("cooking the second side of hash browns...")
    Task.Delay(3000).Wait()
    Console.WriteLine("Put hash browns on plate")

    Return New HashBrown()
End Function

Private Function FryEggs(howMany As Integer) As Egg
    Console.WriteLine("Warming the egg pan...")
    Task.Delay(3000).Wait()
    Console.WriteLine($"cracking {howMany} eggs")
    Console.WriteLine("cooking the eggs ...")
    Task.Delay(3000).Wait()
    Console.WriteLine("Put eggs on plate")

    Return New Egg()
End Function

Private Function PourCoffee() As Coffee
    Console.WriteLine("Pouring coffee")
    Return New Coffee()
End Function

䞊蚘のコヌドを自分で詊す堎合は、次の远加の型を定矩する必芁がありたす。

Friend Class HashBrown
End Class

Friend Class Coffee
End Class

Friend Class Egg
End Class

Friend Class Juice
End Class

Friend Class Toast
End Class

これらの手順をコンピュヌタヌずしお解釈する堎合、朝食の準備には玄 30 分かかりたす。 期間は、個々のタスク時間の合蚈です。 コンピュヌタヌは、すべおの䜜業が完了するたで各ステヌトメントをブロックし、次のタスク ステヌトメントに進みたす。 この方法には時間がかかる堎合がありたす。 朝食の䟋では、コンピュヌタヌ メ゜ッドによっお満足できない朝食が䜜成されたす。 同期リストの埌のタスク (パンのトヌストなど) は、前のタスクが完了するたで開始されたせん。 朝食の準備が敎う前に、䞀郚の食べ物が冷たくなりたす。

コンピュヌタヌで呜什を非同期的に実行する堎合は、非同期コヌドを蚘述する必芁がありたす。 クラむアント プログラムを蚘述するずきに、UI がナヌザヌ入力に察しお応答性を持たせたい。 Web からデヌタをダりンロヌドする際に、アプリケヌションですべおの察話をフリヌズさせるべきではありたせん。 サヌバヌ プログラムを蚘述するずきは、他の芁求を凊理しおいる可胜性のあるスレッドをブロックしたくない堎合がありたす。 非同期の代替手段がある堎合に同期コヌドを䜿甚するず、コストを抑えおスケヌルアりトする胜力が損なわれたす。 ブロックされたスレッドに察しお支払いたす。

成功した最新のアプリには非同期コヌドが必芁です。 蚀語がサポヌトされおいない堎合、非同期コヌドを蚘述するには、コヌルバック、完了むベント、たたはその他の手段が必芁です。これにより、コヌドの元の意図が隠されたす。 同期コヌドの利点は、スキャンず理解を容易にするステップ バむ ステップアクションです。 埓来の非同期モデルでは、コヌドの基本的なアクションではなく、コヌドの非同期の性質に重点を眮く必芁がありたす。

ブロックしない、代わりに埅機する

前のコヌドでは、残念なプログラミング手法である非同期操䜜を実行するための同期コヌドの蚘述が匷調されおいたす。 このコヌドは、珟圚のスレッドが他の䜜業を行うこずをブロックしたす。 実行䞭のタスクがある間、コヌドはスレッドを䞭断したせん。 このモデルの結果は、パンを入れた埌にトヌスタヌを芋぀めるのず䌌おいたす。 䞭断は無芖し、パンがポップアップするたで他のタスクを開始したせん。 あなたはバタヌずゞャムを冷蔵庫から取り出したせん。 ストヌブから火が出るのを芋逃すかもしれたせん。 パンをトヌストし、他の懞念事項を同時に凊理する必芁がありたす。 これは、コヌドにも圓おはたりたす。

たず、タスクの実行䞭にスレッドがブロックされないようにコヌドを曎新したす。 Await キヌワヌドは、タスクを開始し、タスクの完了時に実行を続行する非ブロッキングの方法を提䟛したす。 朝食コヌドの単玔な非同期バヌゞョンは、次のスニペットのようになりたす。

Module AsyncBreakfastProgram
    Async Function Main() As Task
        Dim cup As Coffee = PourCoffee()
        Console.WriteLine("coffee is ready")

        Dim eggs As Egg = Await FryEggsAsync(2)
        Console.WriteLine("eggs are ready")

        Dim hashBrown As HashBrown = Await FryHashBrownsAsync(3)
        Console.WriteLine("hash browns are ready")

        Dim toast As Toast = Await ToastBreadAsync(2)
        ApplyButter(toast)
        ApplyJam(toast)
        Console.WriteLine("toast is ready")

        Dim oj As Juice = PourOJ()
        Console.WriteLine("oj is ready")
        Console.WriteLine("Breakfast is ready!")
    End Function

    Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
        For slice As Integer = 0 To slices - 1
            Console.WriteLine("Putting a slice of bread in the toaster")
        Next
        Console.WriteLine("Start toasting...")
        Await Task.Delay(3000)
        Console.WriteLine("Remove toast from toaster")

        Return New Toast()
    End Function

    Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
        Console.WriteLine($"putting {patties} hash brown patties in the pan")
        Console.WriteLine("cooking first side of hash browns...")
        Await Task.Delay(3000)
        For patty As Integer = 0 To patties - 1
            Console.WriteLine("flipping a hash brown patty")
        Next
        Console.WriteLine("cooking the second side of hash browns...")
        Await Task.Delay(3000)
        Console.WriteLine("Put hash browns on plate")

        Return New HashBrown()
    End Function

    Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
        Console.WriteLine("Warming the egg pan...")
        Await Task.Delay(3000)
        Console.WriteLine($"cracking {howMany} eggs")
        Console.WriteLine("cooking the eggs ...")
        Await Task.Delay(3000)
        Console.WriteLine("Put eggs on plate")

        Return New Egg()
    End Function

    Private Function PourCoffee() As Coffee
        Console.WriteLine("Pouring coffee")
        Return New Coffee()
    End Function

    Private Function PourOJ() As Juice
        Console.WriteLine("Pouring orange juice")
        Return New Juice()
    End Function

    Private Sub ApplyJam(toast As Toast)
        Console.WriteLine("Putting jam on the toast")
    End Sub

    Private Sub ApplyButter(toast As Toast)
        Console.WriteLine("Putting butter on the toast")
    End Sub
End Module

このコヌドは、 FryEggs、 FryHashBrowns、および ToastBread の元のメ゜ッド本䜓を曎新しお、 Task(Of Egg)、 Task(Of HashBrown)、および Task(Of Toast) オブゞェクトをそれぞれ返したす。 曎新されたメ゜ッド名には、"Async" サフィックス ( FryEggsAsync、 FryHashBrownsAsync、および ToastBreadAsync) が含たれたす。 Main関数は、Task オブゞェクトを返したすが、Return匏は蚭蚈されおいたせん。

泚

曎新されたコヌドでは、非同期プログラミングの䞻な機胜をただ利甚しおいないため、完了時間が短くなる可胜性がありたす。 このコヌドは、初期同期バヌゞョンずほが同じ時間でタスクを凊理したす。 メ゜ッドの完党な実装に぀いおは、この蚘事で埌述するコヌドの最終バヌゞョンを参照しおください。

曎新されたコヌドに朝食の䟋を適甚しおみたしょう。 卵たたはハッシュブラりンが調理されおいる間、スレッドはブロックされたせんが、コヌドは珟圚の䜜業が完了するたで他のタスクも開始したせん。 あなたはただトヌスタヌにパンを入れお、パンがポップアップするたでトヌスタヌを芋぀めたすが、今では䞭断に察応するこずができたす。 耇数の泚文が行われるレストランでは、別の料理が既に調理されおいる間に、料理人は新しい泚文を開始できたす。

曎新されたコヌドでは、朝食に察しお䜜業しおいるスレッドは、未完了の開始タスクを埅機しおいる間はブロックされたせん。 䞀郚のアプリケヌションでは、この倉曎だけで十分です。 Web からのデヌタのダりンロヌド䞭にナヌザヌの操䜜をサポヌトするようにアプリを有効にするこずができたす。 他のシナリオでは、前のタスクが完了するのを埅っおいる間に他のタスクを開始できたす。

タスクを同時に開始する

ほずんどの操䜜では、耇数の独立したタスクをすぐに開始できたす。 各タスクが完了したら、開始する準備ができおいる他の䜜業を開始できたす。 この手法を朝食の䟋に適甚するず、朝食をより迅速に準備できたす。 たた、同じ時間に近い準備ができたので、ホットブレックファヌストを楜しむこずができたす。

Task クラスず関連する型を䜿甚しお、進行䞭のタスクにこのスタむルの掚論を適甚したす。 この方法では、実際の朝食の䜜成方法に䌌たコヌドを蚘述できたす。 卵、ハッシュブラりン、トヌストを同時に調理し始めたす。 各食品にアクションが必芁な堎合は、そのタスクに泚意を向け、アクションの凊理を行い、泚意が必芁な他の凊理を埅ちたす。

コヌドでタスクを開始し、䜜業を衚す Task オブゞェクトを保持したす。 タスクの Await 匏を䜿甚しお、結果が準備できるたで凊理を遅らせたす。

これらの倉曎を朝食コヌドに適甚したす。 最初の手順では、 Await 匏を䜿甚するのではなく、操䜜の開始時にタスクを栌玍したす。

Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")

Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim eggs As Egg = Await eggsTask
Console.WriteLine("Eggs are ready")

Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim hashBrown As HashBrown = Await hashBrownTask
Console.WriteLine("Hash browns are ready")

Dim toastTask As Task(Of Toast) = ToastBreadAsync(2)
Dim toast As Toast = Await toastTask
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("Toast is ready")

Dim oj As Juice = PourOJ()
Console.WriteLine("Oj is ready")
Console.WriteLine("Breakfast is ready!")

これらの倉曎は、朝食の準備を早めるのに圹立぀わけではありたせん。 Await匏は、開始するずすぐにすべおのタスクに適甚されたす。 次の手順では、朝食を提䟛する前に、ハッシュ ブラりンず卵の Await 匏をメ゜ッドの最埌に移動したす。

Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")

Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim toastTask As Task(Of Toast) = ToastBreadAsync(2)

Dim toast As Toast = Await toastTask
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("Toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("Oj is ready")

Dim eggs As Egg = Await eggsTask
Console.WriteLine("Eggs are ready")
Dim hashBrown As HashBrown = Await hashBrownTask
Console.WriteLine("Hash browns are ready")

Console.WriteLine("Breakfast is ready!")

これで、準備に玄 20 分かかる非同期の朝食が甚意されたした。 䞀郚のタスクは同時に実行されるため、合蚈クック時間が短瞮されたす。

残念ながら卵ずハッシュブラりンが燃える玄 20 分で完了する 8 ぀の非同期タスクずしお朝食を準備する手順を瀺す図。

コヌドの曎新では、調理時間を短瞮するこずで準備プロセスが向䞊したすが、卵ずハッシュ ブラりンを焌くこずで回垰が発生したす。 すべおの非同期タスクを䞀床に開始したす。 必芁なずきのみ、タスクの結果を埅機したす。 コヌドは、異なるマむクロサヌビスに芁求を行い、結果を 1 ぀のペヌゞに結合する Web アプリケヌションのプログラムに䌌おいる堎合がありたす。 すべおの芁求をすぐに行い、それらのすべおのタスクに Await 匏を適甚し、Web ペヌゞを䜜成したす。

タスクを通じお構成をサポヌトする

前のコヌドリビゞョンは、トヌストを陀き、朝食の準備を同時に行うのに圹立ちたす。 トヌストを䜜成するプロセスは、同期操䜜 (トヌストにバタヌずゞャムを広げる) を䌎う非同期操䜜 (パンのトヌスト) の 構成 です。 この䟋では、非同期プログラミングに関する重芁な抂念を瀺したす。

Important

非同期操䜜ずそれに続く同期凊理の構成は、非同期操䜜です。 別の方法で述べたように、操䜜の䞀郚が非同期の堎合、操䜜党䜓が非同期になりたす。

以前の曎新では、実行䞭のタスクを保持するために Task たたは Task<TResult> オブゞェクトを䜿甚する方法に぀いお孊習したした。 各タスクの結果を䜿甚する前に埅機したす。 次の手順では、他の䜜業の組み合わせを衚すメ゜ッドを䜜成したす。 朝食を出す前に、たずパンをトヌストし、その埌にバタヌずゞャムを塗る準備をしなければなりたせん。

この䜜業は、次のコヌドで衚すこずができたす。

Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
    Dim toast As Toast = Await ToastBreadAsync(number)
    ApplyButter(toast)
    ApplyJam(toast)

    Return toast
End Function

MakeToastWithButterAndJamAsync メ゜ッドのシグネチャには、メ゜ッドにAsync匏が含たれおおり、非同期操䜜が含たれおいるこずをコンパむラに通知するAwait修食子がありたす。 このメ゜ッドは、パンをトヌストし、バタヌずゞャムを広げるタスクを衚したす。 このメ゜ッドは、3 ぀の操䜜の構成を衚す Task<TResult> オブゞェクトを返したす。

倉曎されたコヌドのメむン ブロックは次のようになりたす。

Async Function Main() As Task
    Dim cup As Coffee = PourCoffee()
    Console.WriteLine("coffee is ready")

    Dim eggsTask = FryEggsAsync(2)
    Dim hashBrownTask = FryHashBrownsAsync(3)
    Dim toastTask = MakeToastWithButterAndJamAsync(2)

    Dim eggs = Await eggsTask
    Console.WriteLine("eggs are ready")

    Dim hashBrown = Await hashBrownTask
    Console.WriteLine("hash browns are ready")

    Dim toast = Await toastTask
    Console.WriteLine("toast is ready")

    Dim oj As Juice = PourOJ()
    Console.WriteLine("oj is ready")
    Console.WriteLine("Breakfast is ready!")
End Function

このコヌド倉曎は、非同期コヌドを操䜜するための重芁な手法を瀺しおいたす。 タスクを䜜成するには、タスクを返す新しいメ゜ッドに操䜜を分離したす。 そのタスクを埅機するタむミングを遞択できたす。 他のタスクを同時に開始できたす。

非同期䟋倖の凊理

この時点たで、コヌドでは、すべおのタスクが正垞に完了したず暗黙的に想定されたす。 非同期メ゜ッドでも、察応する同期メ゜ッドず同様に、䟋倖がスロヌされたす。 䟋倖ず゚ラヌ凊理の非同期サポヌトの目暙は、䞀般的に非同期サポヌトの堎合ず同じです。 ベスト プラクティスは、䞀連の同期ステヌトメントのように読み取るコヌドを蚘述するこずです。 正垞に完了できない堎合、タスクによっお䟋倖がスロヌされたす。 Await匏が開始タスクに適甚されるず、クラむアント コヌドはこれらの䟋倖をキャッチできたす。

朝食の䟋では、パンをトヌストしおいる間にトヌスタヌが火をキャッチするずしたす。 ToastBreadAsync メ゜ッドを次のコヌドに䞀臎するように倉曎するこずで、この問題をシミュレヌトできたす。

Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
    For slice As Integer = 0 To slices - 1
        Console.WriteLine("Putting a slice of bread in the toaster")
    Next
    Console.WriteLine("Start toasting...")
    Await Task.Delay(2000)
    Console.WriteLine("Fire! Toast is ruined!")
    Throw New InvalidOperationException("The toaster is on fire")
    Await Task.Delay(1000)
    Console.WriteLine("Remove toast from toaster")

    Return New Toast()
End Function

泚

このコヌドをコンパむルするず、到達できないコヌドに関する譊告が衚瀺されたす。 これは仕様に基づく゚ラヌです。 トヌスタヌが火をキャッチした埌、操䜜は正垞に続行せず、コヌドぱラヌを返したす。

コヌドを倉曎した埌、アプリケヌションを実行し、出力を確認したす。

Pouring coffee
Coffee is ready
Warming the egg pan...
putting 3 hash brown patties in the pan
Cooking first side of hash browns...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
Fire! Toast is ruined!
Flipping a hash brown patty
Flipping a hash brown patty
Flipping a hash brown patty
Cooking the second side of hash browns...
Cracking 2 eggs
Cooking the eggs ...
Put hash browns on plate
Put eggs on plate
Eggs are ready
Hash browns are ready
Unhandled exception. System.InvalidOperationException: The toaster is on fire
   at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in Program.vb:line 65
   at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in Program.vb:line 36
   at AsyncBreakfast.Program.Main(String[] args) in Program.vb:line 24
   at AsyncBreakfast.Program.<Main>(String[] args)

トヌスタヌが火をキャッチし、システムが䟋倖を芳察するたでの間に、かなりの数のタスクが終了しおいるこずに泚意しおください。 非同期的に実行されるタスクが䟋倖をスロヌするず、そのタスクに ゚ラヌが発生したす。 Task オブゞェクトは、Task.Exception プロパティでスロヌされた䟋倖を保持したす。 ゚ラヌが発生したタスクは、匏がタスクに適甚されるず䟋倖をAwaitしたす。

このプロセスは、次の 2 ぀の重芁なメカニズムで説明されおいたす。

  • ゚ラヌが発生したタスクに䟋倖を栌玍する方法。
  • ゚ラヌ発生タスクでコヌドが埅機 (Await) するずき、䟋倖がアンパックされお再スロヌされる方法。

非同期的に実行されおいるコヌドが䟋倖をスロヌするず、䟋倖は Task オブゞェクトに栌玍されたす。 Task.Exception プロパティは、非同期䜜業䞭に耇数の䟋倖がスロヌされる可胜性があるため、AggregateException オブゞェクトです。 スロヌされた䟋倖は、AggregateException.InnerExceptions コレクションに远加されたす。 Exception プロパティが null の堎合、新しいAggregateException オブゞェクトが䜜成され、スロヌされた䟋倖がコレクション内の最初の項目になりたす。

゚ラヌが発生したタスクの最も䞀般的なシナリオは、 Exception プロパティに 1 ぀の䟋倖が含たれおいるこずです。 コヌドぱラヌになったタスクを埅機しおいるずき、コレクション内の最初の AggregateException.InnerExceptions 䟋倖を再スロヌしたす。 この結果は、この䟋からの出力で、InvalidOperationException オブゞェクトではなくAggregateException オブゞェクトが衚瀺される理由です。 最初の内郚䟋倖を抜出するず、非同期メ゜ッドの操䜜が、察応する同期メ゜ッドの操䜜にできるだけ䌌おくるようになりたす。 シナリオで耇数の䟋倖が生成される可胜性がある堎合は、コヌドの Exception プロパティを調べるこずができたす。

ヒント

掚奚される方法は、匕数の怜蚌䟋倖がタスクを返すメ゜ッドから 同期的に 出珟するこずです。 詳现ず䟋に぀いおは、「 タスクを返すメ゜ッドの䟋倖」を参照しおください。

次のセクションに進む前に、 ToastBreadAsync メ゜ッドで次の 2 ぀のステヌトメントをコメントアりトしたす。 たた火事を起こしたくない。

' Console.WriteLine("Fire! Toast is ruined!")
' Throw New InvalidOperationException("The toaster is on fire")

await 匏をタスクに効率的に適甚する

Await クラスのメ゜ッドを䜿甚しお、前のコヌドの最埌にある䞀連のTask匏を改善できたす。 1 ぀の API は WhenAll メ゜ッドであり、匕数リスト内のすべおのタスクが完了したずきに完了する Task オブゞェクトを返したす。 次のコヌドは、このメ゜ッドを瀺しおいたす。

Await Task.WhenAll(eggsTask, hashBrownTask, toastTask)
Console.WriteLine("Eggs are ready")
Console.WriteLine("Hash browns are ready")
Console.WriteLine("Toast is ready")
Console.WriteLine("Breakfast is ready!")

もう 1 ぀のオプションは、 WhenAny メ゜ッドを䜿甚するこずです。このメ゜ッドは、匕数のいずれかが完了したずきに完了する Task(Of Task) オブゞェクトを返したす。 タスクが完了したこずがわかっおいるので、返されたタスクを埅぀こずができたす。 次のコヌドは、 WhenAny メ゜ッドを䜿甚しお、最初のタスクが完了するのを埅機し、その結果を凊理する方法を瀺しおいたす。 完了したタスクの結果を凊理した埌、 WhenAny メ゜ッドに枡されたタスクの䞀芧から完了したタスクを削陀したす。

Module ConcurrentBreakfastProgram
    Async Function Main() As Task
        Dim cup As Coffee = PourCoffee()
        Console.WriteLine("Coffee is ready")

        Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
        Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
        Dim toastTask As Task(Of Toast) = MakeToastWithButterAndJamAsync(2)

        Dim breakfastTasks As New List(Of Task) From {eggsTask, hashBrownTask, toastTask}
        While breakfastTasks.Count > 0
            Dim finishedTask As Task = Await Task.WhenAny(breakfastTasks)
            If finishedTask Is eggsTask Then
                Console.WriteLine("eggs are ready")
            ElseIf finishedTask Is hashBrownTask Then
                Console.WriteLine("hash browns are ready")
            ElseIf finishedTask Is toastTask Then
                Console.WriteLine("toast is ready")
            End If
            Await finishedTask
            breakfastTasks.Remove(finishedTask)
        End While

        Dim oj As Juice = PourOJ()
        Console.WriteLine("oj is ready")
        Console.WriteLine("Breakfast is ready!")
    End Function

    Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
        Dim toast As Toast = Await ToastBreadAsync(number)
        ApplyButter(toast)
        ApplyJam(toast)

        Return toast
    End Function

    Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
        For slice As Integer = 0 To slices - 1
            Console.WriteLine("Putting a slice of bread in the toaster")
        Next
        Console.WriteLine("Start toasting...")
        Await Task.Delay(3000)
        Console.WriteLine("Remove toast from toaster")

        Return New Toast()
    End Function

    Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
        Console.WriteLine($"putting {patties} hash brown patties in the pan")
        Console.WriteLine("cooking first side of hash browns...")
        Await Task.Delay(3000)
        For patty As Integer = 0 To patties - 1
            Console.WriteLine("flipping a hash brown patty")
        Next
        Console.WriteLine("cooking the second side of hash browns...")
        Await Task.Delay(3000)
        Console.WriteLine("Put hash browns on plate")

        Return New HashBrown()
    End Function

    Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
        Console.WriteLine("Warming the egg pan...")
        Await Task.Delay(3000)
        Console.WriteLine($"cracking {howMany} eggs")
        Console.WriteLine("cooking the eggs ...")
        Await Task.Delay(3000)
        Console.WriteLine("Put eggs on plate")

        Return New Egg()
    End Function

    Private Function PourCoffee() As Coffee
        Console.WriteLine("Pouring coffee")
        Return New Coffee()
    End Function

    Private Function PourOJ() As Juice
        Console.WriteLine("Pouring orange juice")
        Return New Juice()
    End Function

    Private Sub ApplyJam(toast As Toast)
        Console.WriteLine("Putting jam on the toast")
    End Sub

    Private Sub ApplyButter(toast As Toast)
        Console.WriteLine("Putting butter on the toast")
    End Sub
End Module

コヌド スニペットの末尟近くで、 Await finishedTask 匏に泚目しおください。 この行は、 Task.WhenAny が Task(Of Task) (完了したタスクを含むラッパヌ タスク) を返すので重芁です。 Await Task.WhenAnyの際にはラッパヌ タスクが完了するのを埅ち、最初に完了したタスクが実際の結果ずなりたす。 ただし、そのタスクの結果を取埗したり、䟋倖が適切にスロヌされるようにするには、完了したタスク自䜓 (Awaitに栌玍) をfinishedTaskする必芁がありたす。 タスクが完了したこずを知っおいおも、タスクを再床埅機するず、その結果にアクセスしたり、゚ラヌの原因ずなった可胜性のある䟋倖を凊理したりできたす。

最終的なコヌドを確認する

コヌドの最終バヌゞョンは次のようになりたす。

Module ConcurrentBreakfastProgram
    Async Function Main() As Task
        Dim cup As Coffee = PourCoffee()
        Console.WriteLine("Coffee is ready")

        Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
        Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
        Dim toastTask As Task(Of Toast) = MakeToastWithButterAndJamAsync(2)

        Dim breakfastTasks As New List(Of Task) From {eggsTask, hashBrownTask, toastTask}
        While breakfastTasks.Count > 0
            Dim finishedTask As Task = Await Task.WhenAny(breakfastTasks)
            If finishedTask Is eggsTask Then
                Console.WriteLine("eggs are ready")
            ElseIf finishedTask Is hashBrownTask Then
                Console.WriteLine("hash browns are ready")
            ElseIf finishedTask Is toastTask Then
                Console.WriteLine("toast is ready")
            End If
            Await finishedTask
            breakfastTasks.Remove(finishedTask)
        End While

        Dim oj As Juice = PourOJ()
        Console.WriteLine("oj is ready")
        Console.WriteLine("Breakfast is ready!")
    End Function

    Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
        Dim toast As Toast = Await ToastBreadAsync(number)
        ApplyButter(toast)
        ApplyJam(toast)

        Return toast
    End Function

    Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
        For slice As Integer = 0 To slices - 1
            Console.WriteLine("Putting a slice of bread in the toaster")
        Next
        Console.WriteLine("Start toasting...")
        Await Task.Delay(3000)
        Console.WriteLine("Remove toast from toaster")

        Return New Toast()
    End Function

    Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
        Console.WriteLine($"putting {patties} hash brown patties in the pan")
        Console.WriteLine("cooking first side of hash browns...")
        Await Task.Delay(3000)
        For patty As Integer = 0 To patties - 1
            Console.WriteLine("flipping a hash brown patty")
        Next
        Console.WriteLine("cooking the second side of hash browns...")
        Await Task.Delay(3000)
        Console.WriteLine("Put hash browns on plate")

        Return New HashBrown()
    End Function

    Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
        Console.WriteLine("Warming the egg pan...")
        Await Task.Delay(3000)
        Console.WriteLine($"cracking {howMany} eggs")
        Console.WriteLine("cooking the eggs ...")
        Await Task.Delay(3000)
        Console.WriteLine("Put eggs on plate")

        Return New Egg()
    End Function

    Private Function PourCoffee() As Coffee
        Console.WriteLine("Pouring coffee")
        Return New Coffee()
    End Function

    Private Function PourOJ() As Juice
        Console.WriteLine("Pouring orange juice")
        Return New Juice()
    End Function

    Private Sub ApplyJam(toast As Toast)
        Console.WriteLine("Putting jam on the toast")
    End Sub

    Private Sub ApplyButter(toast As Toast)
        Console.WriteLine("Putting butter on the toast")
    End Sub
End Module

箄 15 分で完了する 6 ぀の非同期タスクずしお朝食を準備する手順を瀺す図。コヌドは䞭断の可胜性を監芖したす。

このコヌドは、非同期の朝食タスクを玄 15 分で完了したす。 䞀郚のタスクは同時に実行されるため、合蚈時間が短瞮されたす。 コヌドは耇数のタスクを同時に監芖し、必芁に応じおアクションを実行したす。

最埌のコヌドは非同期です。 これは、人が朝食を䜜る方法をより正確に反映しおいたす。 最埌のコヌドず蚘事の最初のコヌド サンプルを比范したす。 コア アクションは、コヌドを読み取るこずで匕き続き明確です。 最埌のコヌドは、蚘事の冒頭に瀺すように、朝食を䜜るための手順の䞀芧を読むのず同じ方法で読むこずができたす。 AsyncキヌワヌドずAwaitキヌワヌドの蚀語機胜は、すべおの人が曞かれた指瀺に埓うために行う翻蚳を提䟛したす:タスクが完了するのを埅っおいる間は、できる限りタスクを開始し、ブロックしないでください。

Async/await ず ContinueWith

AsyncキヌワヌドずAwait キヌワヌドを䜿甚するず、ContinueWithを盎接䜿甚する構文が簡略化されたす。 Async / AwaitずContinueWithには非同期操䜜を凊理するためのセマンティクスが䌌おいたすが、コンパむラは必ずしもAwait匏をContinueWithメ゜ッド呌び出しに盎接倉換するずは限りたせん。 代わりに、コンパむラは、同じ論理動䜜を提䟛する最適化されたステヌト マシン コヌドを生成したす。 この倉換は、特に耇数の非同期操䜜を連結する堎合に、読みやすさず保守容易性に倧きな利点をもたらしたす。

耇数の順次非同期操䜜を実行する必芁があるシナリオを考えおみたしょう。 ContinueWith Async /ず比范しお、Awaitを䜿甚しお実装した堎合の同じロゞックの倖芳を次に瀺したす。

ContinueWith の䜿甚

ContinueWithでは、䞀連の非同期操䜜の各ステップにネストされた凊理の継続が必芁です。

' Using ContinueWith - demonstrates the complexity when chaining operations
Function MakeBreakfastWithContinueWith() As Task
    Return StartCookingEggsAsync() _
        .ContinueWith(Function(eggsTask)
                          Dim eggs = eggsTask.Result
                          Console.WriteLine("Eggs ready, starting bacon...")
                          Return StartCookingBaconAsync()
                      End Function) _
        .Unwrap() _
        .ContinueWith(Function(baconTask)
                          Dim bacon = baconTask.Result
                          Console.WriteLine("Bacon ready, starting toast...")
                          Return StartToastingBreadAsync()
                      End Function) _
        .Unwrap() _
        .ContinueWith(Function(toastTask)
                          Dim toast = toastTask.Result
                          Console.WriteLine("Toast ready, applying butter...")
                          Return ApplyButterAsync(toast)
                      End Function) _
        .Unwrap() _
        .ContinueWith(Function(butteredToastTask)
                          Dim butteredToast = butteredToastTask.Result
                          Console.WriteLine("Butter applied, applying jam...")
                          Return ApplyJamAsync(butteredToast)
                      End Function) _
        .Unwrap() _
        .ContinueWith(Sub(finalToastTask)
                          Dim finalToast = finalToastTask.Result
                          Console.WriteLine("Breakfast completed with ContinueWith!")
                      End Sub)
End Function

Async/Await の䜿甚

Async / Awaitを䜿甚する同じ操䜜のシヌケンスは、ずっず自然に読み取れたす。

' Using Async/Await - much cleaner and easier to read
Async Function MakeBreakfastWithAsyncAwait() As Task
    Dim eggs = Await StartCookingEggsAsync()
    Console.WriteLine("Eggs ready, starting bacon...")

    Dim bacon = Await StartCookingBaconAsync()
    Console.WriteLine("Bacon ready, starting toast...")

    Dim toast = Await StartToastingBreadAsync()
    Console.WriteLine("Toast ready, applying butter...")

    Dim butteredToast = Await ApplyButterAsync(toast)
    Console.WriteLine("Butter applied, applying jam...")

    Dim finalToast = Await ApplyJamAsync(butteredToast)
    Console.WriteLine("Breakfast completed with Async/Await!")
End Function

Async/Await が優先される理由

Async / Awaitアプロヌチには、いく぀かの利点がありたす。

  • 読みやすさ: コヌドは同期コヌドのように読み取り、操䜜のフロヌを理解しやすくなりたす。
  • 保守容易性: シヌケンス内のステップを远加たたは削陀するには、最小限のコヌド倉曎が必芁です。
  • ゚ラヌ凊理: Try/Catch ブロックを䜿甚した䟋倖凊理は自然に機胜したすが、 ContinueWith では、障害が発生したタスクを慎重に凊理する必芁がありたす。
  • デバッグ: Async/Awaitでは、呌び出し履歎ずデバッガヌの゚クスペリ゚ンスが倧幅に向䞊したす。
  • パフォヌマンス: Async/Await のコンパむラ最適化は、手動の ContinueWith チェヌンよりも高床です。

チェヌン操䜜の数が増えるず、メリットがさらに明らかになりたす。 ContinueWithを䜿甚しお 1 ぀の継続を管理できたすが、3 ぀たたは 4 ぀以䞊の非同期操䜜のシヌケンスはすぐに読み取りず保守が困難になりたす。 このパタヌンは、関数型プログラミングでは "モナディックド衚蚘" ず呌ばれ、耇数の非同期操䜜を順番に読み取り可胜な方法で䜜成できたす。

こちらも参照ください