非同期処理の例え話(簡易版)

2022年12月19日

朝食を作って食べる時を例に考えましょう

朝食の用意

ここでは、身近な例として朝食を作るときの料理手順を考えてみましょう

順番

  1. フライパンを熱し、卵を焼く
  2. コーヒーを淹れる
  3. 皿を並べる

イメージ

前の処理が終わると次の処理といったような継続処理を指します

同期処理(順次実行)では、1が終わったら2、2が終わったら3・・・と処理されます
卵を焼いている間、お皿は並べないのです

コード

まずは順番に実行する様子をコードにしてみましょう
この手順の方法を同期処理と呼びます

一番簡単なコードは次のようになります

Console.WriteLine("卵を焼く");
Console.WriteLine("コーヒーを淹れる");
Console.WriteLine("皿を並べる");

結果

実行結果を見てみましょう
イメージの通り、順番に実行されているのがわかります

朝食を並行して準備する

イメージ

準備を同時に実行するイメージです
卵を焼きながら、コーヒーを淹れ、お皿も用意します
実際はこのようにしますよね
一つが終わるまで、じっと待ったりしません

同期コードを非同期に変更して、確認してみましょう

まず、上記のコードを少し変更しておきます

朝食クラスにカスタマイズ

朝食の用意ごとにメソッドを作ります

非同期コードに変更

同時実行できるように変更します
次のコードで3000ms(3秒)待ちます
3秒経過しないと下の行には進めません
ただし、待つ間は制御が呼び出し元に戻ります
システムがロックすることがないのが特徴です

await Task.Delay(3000);
Thread.Sleep(20000);
Console.WriteLine("朝食を母親(妻、etc)にお願い開始");

Console.WriteLine("卵を焼く依頼");
FlyEggAsync();

Console.WriteLine("コーヒーを淹れる依頼");
PourCoffeeAsync();

Console.WriteLine("皿を並べる依頼");
ArrangeDishAsync();

Console.WriteLine("依頼を終えた");

Console.ReadLine();

async void FlyEggAsync()
{
    Console.WriteLine("卵を焼き始める");
    await Task.Delay(7000);
    Console.WriteLine("卵を焼き終わり");
}

async void PourCoffeeAsync()
{
    Console.WriteLine("コーヒーを淹れ始める");
    await Task.Delay(5000);
    Console.WriteLine("コーヒーを淹れ終わり");
}

async void ArrangeDishAsync()
{
    Console.WriteLine("皿を並べ始める");
    await Task.Delay(2000);
    Console.WriteLine("皿を並べ終わり");
}

結果

実行結果を見てみましょう
わかりやすくするためにスタートは3つの処理同時としています
それぞれのメッセージとコードの関係を確認してみましょう

まとめ

非同期処理はどのような時に必要でしょう

  • ネットワークから大きなデータをダウンロード中に他の処理をしたい場合
  • 複数のセンサーからの情報を同時に取得したい場合(各センサーからの受信に時間がかかるケース)

などがありますね

参考

おまけ

すべての処理が終わった後に処理したいことがある場合

//Thread.Sleep(20000);
Console.WriteLine("朝食を母親(妻、etc)にお願い開始");

Console.WriteLine("卵を焼く依頼");
Task<string> flyEggTask = FlyEggAsync();

Console.WriteLine("コーヒーを淹れる依頼");
Task<string> pourCoffeeTask = PourCoffeeAsync();

Console.WriteLine("皿を並べる依頼");
Task<string> arrangeDishTask = ArrangeDishAsync();

Console.WriteLine("依頼を終えた");

Task.WaitAll(new Task[] { flyEggTask, pourCoffeeTask, arrangeDishTask });

Console.WriteLine(flyEggTask.Result);
Console.WriteLine(pourCoffeeTask.Result);
Console.WriteLine(arrangeDishTask.Result);
Console.WriteLine("僕は朝食をいただいた");

async Task<string> FlyEggAsync()
{
    Console.WriteLine("卵を焼き始める");
    await Task.Delay(7000);
    return "卵を焼き終わり";
}

async Task<string> PourCoffeeAsync()
{
    Console.WriteLine("コーヒーを淹れ始める");
    await Task.Delay(5000);
    return "コーヒーを淹れ終わり";
}

async Task<string> ArrangeDishAsync()
{
    Console.WriteLine("皿を並べ始める");
    await Task.Delay(2000);
    return "皿を並べ終わり";
}

個別の完了を知りたい場合

//Thread.Sleep(20000);
Console.WriteLine("朝食を母親(妻、etc)にお願い開始");

Console.WriteLine("卵を焼く依頼");
Task<string> flyEggTask = FlyEggAsync();

Console.WriteLine("コーヒーを淹れる依頼");
Task<string> pourCoffeeTask = PourCoffeeAsync();

Console.WriteLine("皿を並べる依頼");
Task<string> arrangeDishTask = ArrangeDishAsync();

Console.WriteLine("依頼を終えた");

var breakfastTasks = new List<Task> { flyEggTask, pourCoffeeTask, arrangeDishTask };

while (breakfastTasks.Count > 0)
{
    Task finishedTask = await Task.WhenAny(breakfastTasks);
    if (finishedTask == flyEggTask)
    {
        Console.WriteLine("卵焼きできましたよ");
    }
    else if (finishedTask == pourCoffeeTask)
    {
        Console.WriteLine("コーヒーを淹れましたよ");
    }
    else if (finishedTask == arrangeDishTask)
    {
        Console.WriteLine("お皿を並べ終わりましたよ");
    }
    breakfastTasks.Remove(finishedTask);
}

async Task<string> FlyEggAsync()
{
    Console.WriteLine("卵を焼き始める");
    await Task.Delay(7000);
    return "卵を焼き終わり";
}

async Task<string> PourCoffeeAsync()
{
    Console.WriteLine("コーヒーを淹れ始める");
    await Task.Delay(5000);
    return "コーヒーを淹れ終わり";
}

async Task<string> ArrangeDishAsync()
{
    Console.WriteLine("皿を並べ始める");
    await Task.Delay(2000);
    return "皿を並べ終わり";
}

C#

Posted by hidepon