【C#】Task/async/await を使った非同期・並列処理

【検証環境】.NET 6.0

非同期処理

同期処理と非同期処理の違いは下記の通りです。

同期処理

同期処理は処理を順番に行っていくことです。例えばGUIアプリのクリックイベントで、時間のかかる処理を実行した際に、処理が完了するまで画面がフリーズしてしまいます。

非同期処理

非同期処理では、時間のかかる処理の実行中にフリーズすることなく画面を操作することができるようになります。重たい処理を別のスレッドで行うということです。

非同期処理の使い方

自分でメソッドを定義できる場合は非同期メソッドを定義すれば良いですが、外部のライブラリを利用する場合など、同期メソッドしかない(自分で定義ができない)場合もあります。

そのため、同期メソッドを非同期処理する方法と、非同期メソッドを実行する2パターン記述します。

同期メソッドを非同期で実行

同期メソッドを非同期実行する場合、Task.Run()で囲う必要があります

// 同期メソッドを非同期処理
await Task.Run(() => HeavyProcess());

static void HeavyProcess()
{
    // 重たい処理
}

非同期メソッドを実行

非同期メソッドは async Task をメソッド名の前に付けて定義します

また、メソッド名の末尾に「Async」を付ける暗黙の了解があります

// 非同期メソッドを実行
await HeavyProcessAsync();

static async Task HeavyProcessAsync()
{
    // 重たい処理
}

非同期メソッドの定義

非同期メソッドに戻り値を設定することもできます

Task<TResult>型で定義します。戻り値のない場合は、Task型で定義します。

static async Task<string> HeavyProcessAsync()
{
    string result = await SampleAsync();
    return result;
}

static async Task HeavyProcessAsync()
{
    await SampleAsync();
}

await の必要性

await を頭につけることで、指定したTaskが完了するまで待機します。完了後に後続の処理を再開します。しかし、必ずしも必要ではありません。戻り値を受け取る必要がある場合に付ければ良いです。

戻り値が後続の処理に影響ない場合などはawait は無くても良いです。

しかし、await を付けないとVisualStudioで下記のように警告されます。

下記のようにすれば警告は消えます(戻り値を利用しないことを明示)

void Method()
{
    // awaitを使わない
    var _ = Process1Async(); 
}

並列処理

並列処理は、複数の処理を並列で実行し処理時間を短縮することができます

単純に説明すると、2秒かかる処理Aと3秒かかる処理Bがあったとして、順番に実行すると5秒かかるのが、並列で実行すれば3秒でどちらも完了できるよね、ということです。

並列処理の使い方

並列実行しない場合

一旦並列実行しない場合の処理を書いてみます

実行結果は、5021ミリ秒でした。順番に実行しているため、約5秒になります。

var sw = Stopwatch.StartNew();

// 並列処理をしない場合
await Process1Async();
await Process2Async();
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds + "ミリ秒");
// 5021ミリ秒

static async Task Process1Async()
{
    await Task.Delay(2000);
}
static async Task Process2Async()
{
    await Task.Delay(3000);
}

並列実行した場合

Taskのリストを生成し、並列処理したいTaskを要素に追加します

そしてそれを Task.WhenAll() に渡すことで、すべてのTaskが完了するまで待機するようにできます

実行結果は3027ミリ秒でした。並列に実行するため、実行時間の短縮につながりました。

var sw = Stopwatch.StartNew();

var tasks = new List<Task>
{
    Process1Async(),
    Process2Async(),
};
//すべてのtaskが完了するのを待つ
await Task.WhenAll(tasks);

sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds + "ミリ秒");
// 3027ミリ秒

static async Task Process1Async()
{
    await Task.Delay(2000);
}
static async Task Process2Async()
{
    await Task.Delay(3000);
}

参考サイト(必読)

非同期処理、なにもわからない

https://biotech-lab.org/articles/9719

https://zenn.dev/vatscy/articles/d4782637dd4257ef9822

Leave a Reply

Your email address will not be published. Required fields are marked *