次の方法で共有


async と await を使用した非同期プログラミング

Task 非同期プログラミング (TAP) モデルは、一般的な非同期コーディングよりも抽象化のレイヤーを提供します。 このモデルでは、通常と同じように、ステートメントのシーケンスとしてコードを記述します。 違いは、コンパイラが各ステートメントを処理し、次のステートメントの処理を開始する前に、タスク ベースのコードを読み取ることができる点です。 このモデルを実現するために、コンパイラは多くの変換を実行して各タスクを完了します。 一部のステートメントは作業を開始し、進行中の作業を表す Task オブジェクトを返すことができます。コンパイラはこれらの変換を解決する必要があります。 タスク非同期プログラミングの目的は、ステートメントのシーケンスのように読み取るが、より複雑な順序で実行されるコードを有効にすることです。 実行は、外部リソースの割り当てとタスクの完了に基づいています。

タスク非同期プログラミング モデルは、非同期タスクを含むプロセスの指示をユーザーが行う方法に似ています。 この記事では、朝食を作成する手順の例を使用して、 async キーワードと await キーワードを使用して、一連の非同期命令を含むコードを簡単に推論できるようにする方法を示します。 朝食を作る手順は、一覧として提供される場合があります。

  1. コーヒーを一杯注ぎます。
  2. フライパンを熱し、2個の卵を揚げます。
  3. 3スライスのソーセージを揚げます。
  4. 2 枚のパンをトーストします。
  5. トーストにバターとジャムを広げます。
  6. オレンジジュースを1杯注ぎます。

調理の経験がある場合は、これらの手順を 非同期的に完了できます。 あなたは卵のためにパンを温め始め、その後、ソーセージを揚げ始めます。 あなたはトースターにパンを入れて、卵を調理し始めます。 プロセスの各ステップで、まずタスクを開始し、その後、注意を払う準備が整った他のタスクに移行します。

朝食の調理は、並列ではない非同期作業の良い例です。 1 人のユーザー (またはスレッド) がすべてのタスクを処理できます。 前のタスクが完了する前に次のタスクを開始することで、1 人のユーザーが朝食を非同期的に行うことができます。 誰かがプロセスを積極的に見ているかどうかに関係なく、各調理タスクが進行します。 卵のためにフライパンを温め始めたらすぐに、ベーコンを炒め始めることができます。 ベーコンが焼け始めたら、トースターにパンを入れることができます。

並列アルゴリズムの場合は、調理する複数のユーザー (または複数のスレッド) が必要です。 一人の人が卵を調理し、別の人がソーセージをフライします。 各ユーザーは、1 つの特定のタスクに焦点を当てます。 調理中のそれぞれの人 (または各スレッド) がブロックされ、現在のタスクが完了するまで同期的に待機します。ベーコンは裏返す準備ができ、トースターのパンは今にも飛び出しそう、などです。

30 分で完了した 7 つの順次タスクの一覧として朝食を準備する手順を示す図。

C# コード ステートメントとして記述された同期命令の同じ一覧を考えてみましょう。

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static void Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

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

            Bacon bacon = FryBacon(3);
            Console.WriteLine("bacon is ready");

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

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static Egg FryEggs(int howMany)
        {
            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();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

これらの手順をコンピューターとして解釈する場合、朝食の準備には約 30 分かかります。 期間は、個々のタスク時間の合計です。 コンピューターは、すべての作業が完了するまで各ステートメントをブロックし、次のタスク ステートメントに進みます。 この方法には時間がかかる場合があります。 朝食の例では、コンピューター メソッドによって満足できない朝食が作成されます。 同期リストの後のタスク (パンのトーストなど) は、前のタスクが完了するまで開始されません。 朝食の準備が整う前に、一部の食べ物が冷たくなります。

コンピューターで命令を非同期的に実行する場合は、非同期コードを記述する必要があります。 クライアント プログラムを記述するときに、UI がユーザー入力に対して応答性を持たせたい。 Web からデータをダウンロードする際に、アプリケーションですべての対話をフリーズさせるべきではありません。 サーバー プログラムを記述するときは、他の要求を処理している可能性のあるスレッドをブロックしたくない場合があります。 非同期の代替手段がある場合に同期コードを使用すると、コストを抑えてスケールアウトする能力が損なわれます。 ブロックされたスレッドに対して支払います。

成功した最新のアプリには非同期コードが必要です。 言語がサポートされていない場合、非同期コードを記述するには、コールバック、完了イベント、またはその他の手段が必要です。これにより、コードの元の意図が隠されます。 同期コードの利点は、スキャンと理解を容易にするステップ バイ ステップアクションです。 従来の非同期モデルでは、コードの基本的なアクションではなく、コードの非同期の性質に重点を置く必要があります。

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

前のコードでは、残念なプログラミング手法である非同期操作を実行するための同期コードの記述が強調されています。 このコードは、現在のスレッドが他の作業を行うことをブロックします。 実行中のタスクがある間、コードはスレッドを中断しません。 このモデルの結果は、パンを入れた後にトースターを見つめるのと似ています。 中断は無視し、パンがポップアップするまで他のタスクを開始しません。 あなたはバターとジャムを冷蔵庫から取り出しません。 ストーブから火が出るのを見逃すかもしれません。 パンをトーストし、他の懸念事項を同時に処理する必要があります。 コードにも同じことが当てはまります。

まず、タスクの実行中にスレッドがブロックされないようにコードを更新します。 await キーワードは、タスクを開始し、タスクの完了時に実行を続行する非ブロッキングの方法を提供します。 朝食コードの単純な非同期バージョンは、次のスニペットのようになります。

static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");

    Egg eggs = await FryEggsAsync(2);
    Console.WriteLine("eggs are ready");

    Bacon bacon = await FryBaconAsync(3);
    Console.WriteLine("bacon is ready");

    Toast toast = await ToastBreadAsync(2);
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");

    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");
}

このコードは、 FryEggsFryBacon、および ToastBread の元のメソッド本体を更新して、 Task<Egg>Task<Bacon>、および Task<Toast> オブジェクトをそれぞれ返します。 更新されたメソッド名には、"Async" サフィックス ( FryEggsAsyncFryBaconAsync、および ToastBreadAsync) が含まれます。 Main メソッドは、Task オブジェクトを返しますが、return式は設計されていません。 詳細については、「void を 返す非同期関数の評価」を参照してください。

更新されたコードでは、非同期プログラミングの主な機能をまだ利用していないため、完了時間が短くなる可能性があります。 このコードは、初期同期バージョンとほぼ同じ時間でタスクを処理します。 メソッドの完全な実装については、この記事で後述 するコードの最終バージョンを 参照してください。

更新されたコードに朝食の例を適用してみましょう。 卵やソーセージが調理されている間、スレッドはブロックされませんが、コードは現在の作業が完了するまで他のタスクも開始しません。 あなたはまだトースターにパンを入れて、パンがポップアップするまでトースターを見つめますが、今では中断に対応することができます。 複数の注文が行われるレストランでは、別の料理が既に調理されている間に、料理人は新しい注文を開始できます。

更新されたコードでは、朝食に対して作業しているスレッドは、未完了の開始タスクを待機している間はブロックされません。 一部のアプリケーションでは、この変更だけで十分です。 Web からのデータのダウンロード中にユーザーの操作をサポートするようにアプリを有効にすることができます。 他のシナリオでは、前のタスクが完了するのを待っている間に他のタスクを開始できます。

タスクを同時に開始する

ほとんどの操作では、複数の独立したタスクをすぐに開始する必要があります。 各タスクが完了したら、開始する準備ができている他の作業を開始します。 この手法を朝食の例に適用すると、朝食をより迅速に準備できます。 また、同じ時間に近い準備ができたので、ホットブレックファーストを楽しむことができます。

System.Threading.Tasks.Task クラスと関連する型は、進行中のタスクにこのスタイルの推論を適用するために使用できるクラスです。 この方法では、実際の朝食の作成方法に似たコードを記述できます。 卵、ソーセージ、トーストを同時に調理し始めます。 各食品にアクションが必要な場合は、そのタスクに注意を向け、アクションの処理を行い、注意が必要な他の処理を待ちます。

コードでは、タスクを開始し、作業を表す Task オブジェクトを保持します。 作業に対する処理を結果の準備ができるまで遅らせるために、タスクでawait メソッドを使用します。

これらの変更を朝食コードに適用します。 最初の手順では、 await 式を使用するのではなく、操作の開始時にタスクを格納します。

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

Task<Egg> eggsTask = FryEggsAsync(2);
Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");

Task<Bacon> baconTask = FryBaconAsync(3);
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Task<Toast> toastTask = ToastBreadAsync(2);
Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");

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

これらの変更は、朝食の準備を早めるのに役立つわけではありません。 await式は、開始するとすぐにすべてのタスクに適用されます。 次の手順では、朝食を提供する前に、方法の最後に、ソーセージと卵の await 式を移動します。

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

Task<Egg> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(2);

Toast toast = await toastTask;
ApplyButter(toast);
ApplyJam(toast);
Console.WriteLine("Toast is ready");
Juice oj = PourOJ();
Console.WriteLine("Oj is ready");

Egg eggs = await eggsTask;
Console.WriteLine("Eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("Bacon is ready");

Console.WriteLine("Breakfast is ready!");

これで、準備に約 20 分かかる非同期の朝食が用意されました。 一部のタスクは同時に実行されるため、合計クック時間が短縮されます。

残念ながら卵とソーセージが燃える、約 20 分で完了する 8 つの非同期タスクとして朝食を準備する手順を示す図。

コードの更新により、調理時間が短縮され、準備プロセスが改善されますが、卵とソーセージを焼くことで回帰が発生します。 すべての非同期タスクを一度に開始します。 必要なときのみ、タスクの結果を待機します。 コードは、異なるマイクロサービスに要求を行い、結果を 1 つのページに結合する Web アプリケーションのプログラムに似ている場合があります。 すべての要求をすぐに行い、それらのすべてのタスクに await 式を適用し、Web ページを作成します。

タスクを通じて構成をサポートする

前のコードリビジョンは、トーストを除き、朝食の準備を同時に行うのに役立ちます。 トーストを作成するプロセスは、同期操作 (トーストにバターとジャムを広げる) を伴う非同期操作 (パンのトースト) の 構成 です。 この例では、非同期プログラミングに関する重要な概念を示します。

Von Bedeutung

非同期操作とそれに続く同期処理の構成は、非同期操作です。 別の方法で述べたように、操作の一部が非同期の場合、操作全体が非同期になります。

以前の更新では、実行中のタスクを保持するために Task または Task<TResult> オブジェクトを使用する方法について学習しました。 各タスクの結果を使用する前に、そのタスクを待ちます。 次の手順では、他の作業の組み合わせを表すメソッドを作成します。 朝食を出す前に、まずパンをトーストし、その後にバターとジャムを塗る準備をしなければなりません。

この作業は、次のコードで表すことができます。

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
    var toast = await ToastBreadAsync(number);
    ApplyButter(toast);
    ApplyJam(toast);

    return toast;
}

MakeToastWithButterAndJamAsync メソッドのシグネチャには、メソッドにawait式が含まれており、非同期操作が含まれていることをコンパイラに通知するasync修飾子があります。 このメソッドは、パンをトーストし、バターとジャムを広げるタスクを表します。 このメソッドは、3 つの操作の構成を表す Task<TResult> オブジェクトを返します。

変更されたコードのメイン ブロックは次のようになります。

static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");

    var eggsTask = FryEggsAsync(2);
    var baconTask = FryBaconAsync(3);
    var toastTask = MakeToastWithButterAndJamAsync(2);

    var eggs = await eggsTask;
    Console.WriteLine("eggs are ready");

    var bacon = await baconTask;
    Console.WriteLine("bacon is ready");

    var toast = await toastTask;
    Console.WriteLine("toast is ready");

    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");
}

このコード変更は、非同期コードを操作するための重要な手法を示しています。 タスクを作成するには、タスクを返す新しいメソッドに操作を分離します。 そのタスクを待機するタイミングを選択できます。 他のタスクを同時に開始できます。

非同期例外の処理

この時点まで、コードでは、すべてのタスクが正常に完了したと暗黙的に想定されます。 非同期メソッドは、同期メソッドと同じように例外をスローします。 例外とエラー処理の非同期サポートの目標は、一般的に非同期サポートの場合と同じです。 ベスト プラクティスは、一連の同期ステートメントのように読み取るコードを記述することです。 タスクは、正常に完了できない場合に例外をスローします。 await式が開始タスクに適用されると、クライアント コードはこれらの例外をキャッチできます。

朝食の例では、パンをトーストしている間にトースターが火をキャッチするとします。 ToastBreadAsync メソッドを次のコードに一致するように変更することで、この問題をシミュレートできます。

private static async Task<Toast> ToastBreadAsync(int slices)
{
    for (int slice = 0; slice < slices; slice++)
    {
        Console.WriteLine("Putting a slice of bread in the toaster");
    }
    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();
}

このコードをコンパイルすると、到達できないコードに関する警告が表示されます。 これは仕様に基づくエラーです。 トースターが火をキャッチした後、操作は正常に続行せず、コードはエラーを返します。

コードを変更した後、アプリケーションを実行し、出力を確認します。

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

トースターが火をキャッチし、システムが例外を観察するまでの間に、かなりの数のタスクが終了していることに注意してください。 非同期的に実行されるタスクが例外をスローすると、そのタスクに エラーが発生しますTask オブジェクトは、Task.Exception プロパティでスローされた例外を保持します。 エラーが発生したタスクは、 await 式がタスクに適用されると例外をスローします。

このプロセスについて理解するには、次の 2 つの重要なメカニズムがあります。

  • エラーが発生したタスクに例外を格納する方法
  • エラーが発生したタスクでコードが待機 (await) したときに例外をパッケージ化解除して再スローする方法

非同期的に実行されているコードが例外をスローすると、例外は Task オブジェクトに格納されます。 Task.Exception プロパティは、非同期処理中に複数の例外が発生する可能性があるため、System.AggregateException オブジェクトです。 スローされた例外はすべて、 AggregateException.InnerExceptions コレクションに追加されます。 Exception プロパティが null の場合、新しいAggregateException オブジェクトが作成され、スローされた例外がコレクション内の最初の項目になります。

エラーが発生したタスクの最も一般的なシナリオは、 Exception プロパティに 1 つの例外が含まれていることです。 コードが障害のあるタスクを待機すると、コレクション内の最初の AggregateException.InnerExceptions 例外が再スローされます。 この結果は、この例からの出力で、AggregateException オブジェクトではなくSystem.InvalidOperationException オブジェクトが表示される理由です。 最初の内部例外を抽出すると、非同期メソッドの操作が、対応する同期メソッドの操作にできるだけ似てくるようになります。 シナリオで複数の例外が生成される可能性がある場合は、コードの Exception プロパティを調べることができます。

ヒント

推奨される方法は、引数の検証例外がタスクを返すメソッドから 同期的に 出現することです。 詳細と例については、「 タスクを返すメソッドの例外」を参照してください。

次のセクションに進む前に、 ToastBreadAsync メソッドで次の 2 つのステートメントをコメントアウトします。 また火事を起こしたくない。

Console.WriteLine("Fire! Toast is ruined!");
throw new InvalidOperationException("The toaster is on fire");

await 式をタスクに効率的に適用する

Task クラスのメソッドを使用して、前のコードの最後にある一連のawait式を改善できます。 1 つの API は WhenAll メソッドであり、引数リスト内のすべてのタスクが完了したときに完了する Task オブジェクトを返します。 次のコードは、このメソッドを示しています。

await Task.WhenAll(eggsTask, baconTask, toastTask);
Console.WriteLine("Eggs are ready");
Console.WriteLine("Bacon is ready");
Console.WriteLine("Toast is ready");
Console.WriteLine("Breakfast is ready!");

もう 1 つのオプションは、 WhenAny メソッドを使用することです。このメソッドは、引数のいずれかが完了したときに完了する Task<Task> オブジェクトを返します。 タスクが完了したことがわかっているので、返されたタスクを待つことができます。 次のコードは、 WhenAny メソッドを使用して、最初のタスクが完了するのを待機し、その結果を処理する方法を示しています。 完了したタスクの結果を処理した後、 WhenAny メソッドに渡されたタスクの一覧から完了したタスクを削除します。

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
    Task finishedTask = await Task.WhenAny(breakfastTasks);
    if (finishedTask == eggsTask)
    {
        Console.WriteLine("Eggs are ready");
    }
    else if (finishedTask == baconTask)
    {
        Console.WriteLine("Bacon is ready");
    }
    else if (finishedTask == toastTask)
    {
        Console.WriteLine("Toast is ready");
    }
    await finishedTask;
    breakfastTasks.Remove(finishedTask);
}

コード スニペットの末尾近くで、 await finishedTask; 式に注目してください。 await Task.WhenAny式は、完了したタスクを待機するのではなく、Task.WhenAny メソッドによって返されたTask オブジェクトを待機します。 Task.WhenAny メソッドの結果は、完了した (またはエラーが発生した) タスクです。 ベスト プラクティスは、タスクが完了したことがわかっている場合でも、タスクをもう一度待機することです。 この方法では、タスクの結果を取得したり、タスクを失敗させる原因となる例外がスローされることを保証したりできます。

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

コードの最終バージョンは次のようになります。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    // These classes are intentionally empty for the purpose of this example. They are simply marker classes for the purpose of demonstration, contain no properties, and serve no other purpose.
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static async Task Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            var eggsTask = FryEggsAsync(2);
            var baconTask = FryBaconAsync(3);
            var toastTask = MakeToastWithButterAndJamAsync(2);

            var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
            while (breakfastTasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(breakfastTasks);
                if (finishedTask == eggsTask)
                {
                    Console.WriteLine("eggs are ready");
                }
                else if (finishedTask == baconTask)
                {
                    Console.WriteLine("bacon is ready");
                }
                else if (finishedTask == toastTask)
                {
                    Console.WriteLine("toast is ready");
                }
                await finishedTask;
                breakfastTasks.Remove(finishedTask);
            }

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
        {
            var toast = await ToastBreadAsync(number);
            ApplyButter(toast);
            ApplyJam(toast);

            return toast;
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static async Task<Toast> ToastBreadAsync(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            await Task.Delay(3000);
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static async Task<Bacon> FryBaconAsync(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            await Task.Delay(3000);
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            await Task.Delay(3000);
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static async Task<Egg> FryEggsAsync(int howMany)
        {
            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();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

このコードは、非同期の朝食タスクを約 15 分で完了します。 一部のタスクは同時に実行されるため、合計時間が短縮されます。 コードは複数のタスクを同時に監視し、必要に応じてアクションを実行します。

約 15 分で完了する 6 つの非同期タスクとして朝食を準備する手順を示す図。コードは中断の可能性を監視します。

最後のコードは非同期です。 これは、人が朝食を作る方法をより正確に反映しています。 最後のコードと記事の最初のコード サンプルを比較します。 コア アクションは、コードを読み取ることで引き続き明確です。 最後のコードは、記事の冒頭に示すように、朝食を作るための手順の一覧を読むのと同じ方法で読むことができます。 asyncキーワードとawaitキーワードの言語機能は、すべての人が書かれた指示に従うために行う翻訳を提供します:タスクが完了するのを待っている間は、できる限りタスクを開始し、ブロックしないでください。

次のステップ