C# 複数のタスクを待機

Naila Saad Siddiqui 2024年2月15日
  1. C# での同期および非同期プログラミング
  2. C#async メソッドと await 複数のタスクを実装する
C# 複数のタスクを待機

この簡単なガイドでは、非同期プログラミング モデルについて説明し、asyncawaitTask などの関連キーワードについて説明します。 特に、C# を使用して複数のタスクを待機するという概念を示しています。

実際のトピックに移る前に、同期プログラミング モデルと非同期プログラミング モデルの違いを簡単に概説する必要があります。 それでは、始めましょう!

C# での同期および非同期プログラミング

同期モデルと非同期モデルは、コンピューター プログラミングにおいて非常に重要です。 用語は、各プログラミング モデルの機能とその違いに関する情報を提供します。

同期タスクは、通常、1つずつ順番に実行されます。 非同期タスクは同時に (任意の順序で) 実行できます。

C# での同期プログラミング

同期は、リアクティブ プログラミング システムに最適なブロッキング アーキテクチャです。 シングル スレッド モデルとして、厳格な一連のシーケンスに準拠しているため、各操作は一度に 1つずつ正確に連続して実行されます。

1つのプロセスが実行されている間、以降の手順の指示はブロックされます。 1つのタスクが完了すると、次のタスクが開始されます。

同期プログラミングがどのように動作するかを理解するために、トランシーバーの例を考えてみましょう。

通話中に 1 人が話し、もう 1 人が聞いています。 通常、2 人目の人は、1 人目の人が話し終わった直後に応答します。

プログラミングに関して、同期プログラミングを理解するために次の例を考えてみましょう。

using System;
using System.Threading;
public class Program {
  public static void Main(string[] args) {
    Process1();
    Process2();
  }
  static void Process1() {
    Console.WriteLine("Process 1 started");
    // You can write some code here that can take a longer time
    Thread.Sleep(5000);  // holing on execution for 5 sec
    Console.WriteLine("Process 1 Completed");
  }
  static void Process2() {
    Console.WriteLine("Process 2 Started");
    // write some code here that takes relatively less time
    Console.WriteLine("Process 2 Completed");
  }
}

上記の例の Process1() メソッドは、サーバーからファイルを読み取る、大量のデータを返す Web API に接続する、または巨大なファイルをアップロードまたはダウンロードするなど、時間のかかるプロセスを実行するために使用されます。

実行には少し時間がかかります (説明のために、Thread.Sleep(5000) は 5 秒間保持します)。 process2() メソッドは Process1() メソッドに従い、同時に開始しません。

上記のプログラムは同期的に実行されます。 これは、Main() 関数で実行が開始され、その後、Process1() メソッドと Process2() メソッドがそれぞれ順番に実行されることを示します (つまり、厳密に 1つずつ順番に実行されます)。

実行中にアプリケーションが停止し、応答しなくなります (以下の出力画面を参照)。 同期プログラミングは、前の行の実行が完了するまで、次の行に進むのを待機しています。

同期プログラミングを完全に理解するために出力を見てみましょう。

同期プログラミング出力

上記の出力から、Process1 が完了するのを待っている間、実行がブロックされていることがわかります。 その実行が完了するとすぐに、Process2 が実行されます。

C# での非同期プログラミング

一方、非同期プログラミングは、ネットワークと通信に最適なマルチスレッド スタイルです。 非ブロッキング設計として、非同期アーキテクチャは、1つ以上の操作が実行されている間、後続の実行を妨げません。

複数の関連する操作は、他のアクションが完了するのを待たずに、非同期プログラミングで同時に実行できます。 メッセージを受信した直後に返信する代わりに、非同期通信に従事する人は、それを読んで処理する前に、それが便利または実行可能になるまで待ちます。

非同期通信技術にはテキスト メッセージが含まれます。 1 人がテキスト メッセージを送信でき、受信者はいつでも返信できます。 応答を待っている間、送信者は他のアクションを実行できます。

上記の例によると、たとえば Process1() メソッドは、非同期プログラミング モデルのスレッド プールとは別のスレッドで実行されます。 対照的に、メイン アプリケーション スレッドは後続のステートメントを実行し続けます。

Microsoft は、async および await キーワードと Task または Task<TResult> クラスを使用して、.NET Framework または .NET Core アプリケーションで非同期プログラミングを実装するときに、タスク ベースの非同期パターンを使用することをお勧めします。

非同期プログラミングを使用して前のコードを書き直してみましょう。

using System;
using System.Threading.Tasks;

public class Program {
  public static async Task Main(string[] args) {
    Process1();
    Process2();
    Console.ReadKey();
  }

  public static async void Process1() {
    Console.WriteLine("Process 1 Started");
    await Task.Delay(5000);  // holding execution for 5 seconds
    Console.WriteLine("Process 1 Completed");
  }
  static void Process2() {
    Console.WriteLine("Process 2 Started");
    // Write code here
    Console.WriteLine("Process 2 Completed");
  }
}

async キーワードは、上記の例で Main() メソッドを識別するために使用され、Task は戻り値の型です。

このメソッドは、async キーワードによって非同期として識別されます。 非同期プログラミングを実行するには、メソッド チェーン内のすべてのメソッドが async でなければならないことに注意してください。

したがって、子メソッドを非同期にするには、Main() 関数を async にする必要があります。

async キーワードは、Process1() メソッドが非同期であり、Task.Delay(5000); を待っていることを示します。 スレッドが有用な実行を 5 秒間阻止します。

メイン アプリケーション スレッドの async Main() メソッドがプログラムの実行を開始します。 メイン アプリケーション スレッドは、async Process1() が終了するのを待たずに、Process2() メソッドを使用する後続のステートメントを実行し続けます。

このコード セグメントの出力は次のようになります。

非同期プログラミング出力

asyncawait、および Task キーワードの使用

async メソッドが呼び出し元のコードに値を返す場合は、asyncawait および Task と共に使用します。 上記の例では、async キーワードを使用して、基本的な非同期 void メソッドの使用方法を示しました。

async メソッドが値を返す前に、await キーワードがそれを待ちます。 その結果、戻り値を受け取るまで、メイン アプリケーション スレッドがハングします。

Task クラスは非同期操作を表し、結果を返すことができるプロセスはジェネリック型 Task<TResult> で表されます。 上記の場合、await Task が使用されました。 Delay(5000) が 5 秒間スリープする async 操作を開始した後、await は 5 秒間スレッドを保持します。

async メソッドは、以下の例の値を返します。

C#async メソッドと await 複数のタスクを実装する

2つのプロセスがあり、それぞれが値を返し、それらの値がこれら 2つの合計を計算する他の関数に渡される例を考えてみましょう (合計は説明のために計算され、結果に対して任意の操作を実行できます)。

この目的のために、プロセスが完了して値を返すのを待ってから、それらの返された値を他の関数に渡す必要があります。 これにより、WhenAll メソッドが使用されます。

WhenAll メソッドは、すべてのプロセスが完了するのを待ってから、返された結果を保存します。 これは、次の例に示されています。

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

public class Program {
  static async Task Main(string[] args) {
    Task<int> res1 = Process1();
    Task<int> res2 = Process2();

    Console.WriteLine("After two processes.");
    var ls = await Task.WhenAll(res1, res2);
    DoSomeTask(ls);

    Console.ReadKey();
  }

  static async Task<int> Process1() {
    Console.WriteLine("Process 1 Started");
    await Task.Delay(5000);  // hold execution for 5 seconds
    Console.WriteLine("Process 1 Completed");
    return 15;
  }

  static async Task<int> Process2() {
    Console.WriteLine("Process 2 Started");
    await Task.Delay(3000);  // hold execution for 3 seconds
    Console.WriteLine("Process 2 Completed");
    return 25;
  }

  static void DoSomeTask(int[] l) {
    int sum = 0;
    foreach (int i in l) sum += i;
    Console.WriteLine("sum of the list is: {0} ", sum);
  }
}

上記のコード セグメントでは、Process1Process2 という 2つの関数を作成しました。 これらの機能はどちらも 5 秒と 3 秒の遅延があります。

2つのプロセスが完了すると、返された値を DoSomeTask, に渡し、これらの数値の合計を計算して画面に表示します。 このコード セグメントの出力は次のようになります。

戻り値のある非同期プログラミング