なんだかGoodVibes

日々の勉強メモです。

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

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

概要

非同期処理を実現する際に使用可能なものはいくつかありますが
その中でも最も基本であり、レガシーと言われるThreadクラスを使用した
非同期処理についてのメモです。


スレッドの開始と待機(Start、Join)

以下のコードでは、
HeavyProcという時間のかかる処理を非同期で処理しています。

public void Proc()
{
    var t = new Thread(HeavyProc);
    t.Start();

    t.Join();

    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}][Proc] End!");
}

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

実行結果は以下のようになります。

[15:13:25][HeavyProc] i = 0
[15:13:26][HeavyProc] i = 1
[15:13:27][HeavyProc] i = 2
[15:13:28][HeavyProc] i = 3
[15:13:29][HeavyProc] i = 4
[15:13:30][HeavyProc] i = 5
[15:13:31][HeavyProc] i = 6
[15:13:32][HeavyProc] i = 7
[15:13:33][HeavyProc] i = 8
[15:13:34][HeavyProc] i = 9
[15:13:34][Proc] End!

HeavyProcでは1秒間隔でループを10回実行しています。

Procでは、非同期でHeavyProcを実行して待機しています。
開始はt.Start();、待機はt.Join();です。
なので、HeavyProcが終了するのを待機し、
完了した後に処理が終了となっています。


バックグラウンドとフォアグラウンド

new Threadを用いて作成したスレッドは
デフォルトでフォアグラウンドスレッドとなります。
フォアグラウンドスレッドの場合、呼び出し元のスレッドが終了しても
終了せずに処理が終わるまで動作します。

バックグラウンドスレッドの場合、呼び出し元のスレッドが終了すると
処理が終わるのを待機せずに終了します。

先程のサンプルコードを使用して
バックグラウンドを指定すると以下のようになります。

var t = new Thread(HeavyProc)
{
    IsBackground = true
};


スレッドに引数を渡す

以下のサンプルでは、HeavyProcにループ回数を
引数で渡すようにしています。

引数はStart()の引数に与えます。

public void Proc()
{
    var t = new Thread(HeavyProc)
    {
        IsBackground = true
    };

    t.Start(6);

    t.Join();

    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}][Proc] End!");
}

public void HeavyProc(object obj)
{
    int max;
    try
    {
        max = (int) obj;
    }
    catch (Exception)
    {
        max = 10;
    }

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

実行結果は以下のようになります。

[HeavyProc] max = 6
[15:14:38][HeavyProc] i = 0
[15:14:39][HeavyProc] i = 1
[15:14:40][HeavyProc] i = 2
[15:14:41][HeavyProc] i = 3
[15:14:42][HeavyProc] i = 4
[15:14:43][HeavyProc] i = 5
[15:14:43][Proc] End!


スレッドの中断(Abort → 非推奨)

Abortを使用するとスレッドの中断が可能になります。
が、Abortは非推奨となっています。

.Net5以降では以下のようにコンパイル時に警告が発生します。

 warning SYSLIB0006: 'Thread.Abort()' は旧形式です ('Thread.Abort is not supported and throws PlatformNotSupportedException.')

.Net5より前のバージョンでは、
実行時に例外「PlatformNotSupportedException」が発生します。


スレッドの中断(Interrupt)

以下のサンプルでは、Interruptを使用してスレッドを中断しています。
Interruptを呼び出すと、スレッド側でThreadInterruptedExceptionが発生します。
これを検知することで、スレッドの中断が可能となります。

public void Proc()
{
    var t = new Thread(HeavyProc)
    {
        IsBackground = true
    };

    t.Start();
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}][Proc] Thread Start");
    Thread.Sleep(TimeSpan.FromSeconds(1));
    t.Interrupt();

    Thread.Sleep(TimeSpan.FromSeconds(1));
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}][Proc] End!");
}

public void HeavyProc()
{
    try
    {
        Thread.Sleep(TimeSpan.FromSeconds(10));
    }
    catch (ThreadInterruptedException)
    {
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}][HeavyProc] ThreadInterruptedException発生");
    }
}

実行結果は以下のようになります。
HeavyProcは10秒待機する処理ですが、ProcからInterruptが呼び出されることによって
ThreadInterruptedExceptionが発生し、処理が終了しています。

[15:24:08][Proc] Thread Start
[15:24:09][HeavyProc] ThreadInterruptedException発生
[15:24:10][Proc] End!



以上です。