MRが楽しい

MRやVRについて学習したことを書き残す

C#でSemaphoreSlimを使ってスレッド制御を行う

本日は C# の小ネタ枠です。
C# で SemaphoreSlim を使ってスレッド制御を行う方法についてです。

SemaphoreSlim

SemaphoreSlim はリソースに同時にアクセスできるスレッドの数を制限します。
learn.microsoft.com

以下のように同時に実行可能な Task の数を第一引数で指定して利用できます。

// 同時に3つまでのタスクを許容する
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3);

第二引数を指定しない場合、後から Task の数を追加することができます。
追加可能な Task の上限数を制限したい場合は以下のように第一引数と第二引数を指定します。

// 同時に1つまでのタスクを許容し、かつ、タスクの数を後から追加できないようにする
SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);

スレッドをブロックするときは Wait を、解放するときは Release を呼び出します。

// スレッドをブロックする
semaphoreSlim.Wait();

// スレッドを解放する
semaphoreSlim.Release();

// 現在利用可能なスレッド数を確認する
Debug.Log(${semaphoreSlim.CurrentCount});

サンプルシーン

以下のサンプルシーンを作って SemaphoreSlim の動作を確認してみました。
ボタンを押下すると、3秒間カウントダウンを行う処理が呼び出されます。
・SemaphoreSlimTest.cs

using System.Threading.Tasks;
using UnityEngine;
public class SemaphoreSlimTest : MonoBehaviour
{
    public async void ButtonEvent()
    {
        // 1秒ごとに1~3のカウントダウンをログに出力する
        for(int count = 1; count <= 3; count++) {
            Debug.Log(count);
            await Task.Delay(1000);
        }
    }
}

ただし上記スクリプトはスレッド制御を行っていないため、ボタンを連打した場合は別々のカウントダウンが途中で割り込んで表示されてしまいます。

スクリプトを以下のように変更し、SemaphoreSlim を使って2つ以上のタスクが同時実行されないように制限します。
・SemaphoreSlimTest.cs

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
public class SemaphoreSlimTest : MonoBehaviour
{
    // 同時に1つまでのタスクを許容し、かつ、タスクの数を後から追加できないようにする
    private SemaphoreSlim _semaphoreSlim = new (1,1);
    
    public async void ButtonEvent()
    {
        // スレッドをブロックする(非同期で行いたい場合は WaitAsync() を使用する)
        await _semaphoreSlim.WaitAsync();
        // 1秒ごとに1~3のカウントダウンをログに出力する
        for(int count = 1; count <= 3; count++) {
            Debug.Log(count);
            await Task.Delay(1000);
        }
        // スレッドを解放する
        _semaphoreSlim.Release();
    }
}

これでボタンを連打してもカウントダウンが同時実行されず、後のタスクは前のタスク完了を待機して順に実行されるようになりました。

待機処理を途中でキャンセルしたい場合は以下の記事のようにキャンセルトークンを利用します。
bluebirdofoz.hatenablog.com