なんだかGoodVibes

日々の勉強メモです。

【C#】Taskを使用した非同期処理

こんにちは。
本日はTaskを使用した非同期処理についてのメモです。

概要

Taskは.NET Framework 4で導入された
非同期処理を実現するためのクラスです。

非同期処理Threadに関しては、
以下の記事を参考にしてください。

【C#】Threadを使用した非同期処理 - なんだかGoodVibes


スレッドの開始と待機

以下のサンプルでは、重い処理を定義して
その処理を非同期で実施しています。

Action<object> action = (object obj) => {
    int max;
    try
    {
        max = (int) obj;
    }
    catch (Exception)
    {
        max = 10;
    }

    Console.WriteLine($"max = {max}");
    for (var i = 0; i < max; i++)
    {
        Thread.Sleep(TimeSpan.FromSeconds(1));
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] i = {i}");
    }
};

var t = new Task(action, 5);
t.Start();
t.Wait();

Console.WriteLine("Completed!");

以下の処理では、Taskを生成しています。
この時点ではあくまでも生成のみで、開始はしていないので注意してください。

var t = new Task(action, 5);

以下の処理で非同期処理を開始します。

t.Start();

以下の処理で非同期処理を待機します。

t.Wait();

実行結果は以下となります。

max = 5
[13:26:57] i = 0
[13:26:58] i = 1
[13:26:59] i = 2
[13:27:00] i = 3
[13:27:01] i = 4
Completed!


スレッドの開始と待機(Task.Runを使用)

以下のサンプルでは、 .NET Framework 4.5にて導入された
Runを使用して非同期処理を実施します。

var t = Task.Run(() => {
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Start");
    Thread.Sleep(TimeSpan.FromSeconds(3));
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] End");
});

t.Wait();
Console.WriteLine("Completed!");

以下の処理で非同期処理を開始します。

var t = Task.Run(() => {
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Start");
    Thread.Sleep(TimeSpan.FromSeconds(3));
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] End");
});

以下の処理で非同期処理を待機します。

t.Wait();

実行結果は以下です。

[13:32:35] Start
[13:32:38] End
Completed!


複数のタスクの待機

以下の重い処理を複数のタスクで実行した場合のサンプルです。

public void HeavyProc(object obj)
{
    int max = (int) obj;
    for (var i = 0; i < max; i++)
    {
        Thread.Sleep(TimeSpan.FromSeconds(1));
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}][{Task.CurrentId}] i = {i}");
    }
}


複数のタスクのうちどれか一つが終了するのを待機する

指定したタスクのうち、どれか1つでも終了したら
待機を終了するには、WaitAnyを使用します。

var t1 = Task.Run(() => HeavyProc(3));
var t2 = Task.Run(() => HeavyProc(2));
var t3 = Task.Run(() => HeavyProc(1));

Task.WaitAny(new []{t1, t2, t3});
Console.WriteLine("Completed!");

実行結果は以下です。

[13:45:37][1] i = 0
[13:45:37][3] i = 0
[13:45:37][2] i = 0
Completed!

最小のループ回数が1回なので、
1回を指定したタスクが終了すると待機が完了して
処理が進んでいるのがわかります。


すべてのタスクが終了するのを待機する

指定したタスクのうち、すべてが終了してから
待機を終了するには、WaitAllを使用します。

var t1 = Task.Run(() => HeavyProc(3));
var t2 = Task.Run(() => HeavyProc(2));
var t3 = Task.Run(() => HeavyProc(1));

Task.WaitAll(new []{t1, t2, t3});
Console.WriteLine("Completed!");

実行結果は以下です。

[13:45:44][5] i = 0
[13:45:44][6] i = 0
[13:45:44][4] i = 0
[13:45:45][6] i = 1
[13:45:45][4] i = 1
[13:45:46][4] i = 2
Completed!

最大のループ回数が3回なので、
3回を指定したタスクが終了すると待機が完了して
処理が進んでいるのがわかります。 つまり、すべてのタスクが完了するのを待機しています。


async/await で待機

タスクの待機を Waitを使用せず、
await を使用して待機しています。
await を使用する場合、メソッドは

  • async を付与
  • 戻りの型は Task もしくは Task<戻り値の型>

とする必要があります。
今回、戻り値はないので、戻りの型はTaskとしています。

public async Task Proc()
{
    var t = Task.Run(() => HeavyProc(2));
    await t;
    Console.WriteLine("Completed!");
}

実行結果は以下です。

[17:17:40][1] i = 0
[17:17:41][1] i = 1
Completed!



以上です。