MRが楽しい

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

lockObjectとSemaphoreSlimの使い分け

本日は C# の小ネタ枠です。
非同期処理での lockObject と SemaphoreSlim の使い分けについて記事にします。

lockObject

lock ステートメントは指定のオブジェクトに対する相互排他ロックを取得することで、ステートメントブロックの排他制御を行います。
lock ステートメントは最大で 1 つだけのスレッドが実行されます。
learn.microsoft.com

    private object _lockObject = new ();
    
    public async void ButtonEvent()
    {
        lock (_lockObject)
        {
            for (int count = 1; count <= 3; count++)
            {
                Debug.Log(count);
            }
        }
    }

lock ステートメントは非同期処理をブロックできません。
以下のように lock ステートメント内に await を記述すると「Cannot await in the body of a lock statemen」エラーになります。

        lock (_lockObject)
        {
            for (int count = 1; count <= 3; count++)
            {
                Debug.Log(count);
                await Task.Delay(1000); // Cannot await in the body of a lock statemen エラー
            }
        }

これは lock ステートメントは同一メソッドで解放される必要があるが、非同期処理では同一メソッドで解放されることが保証されないためです。
lock ステートメントを利用する際は、非同期メソッド内でもブロックが必要な同期処理部分だけを lock する必要があります。

SemaphoreSlim

SemaphoreSlim はリソースに同時にアクセスできるスレッドの数を制限して排他制御を行います。
SemaphoreSlim は任意のスレッド数を指定することができます。
learn.microsoft.com

SemaphoreSlim は以下のように await を含む非同期処理をブロックすることができます。
await を含む処理部分をブロックしたい場合は SemaphoreSlim を利用して排他制御を行うと良いです。

    private SemaphoreSlim _semaphoreSlim = new (1,1);
    
    public async void ButtonEvent()
    {
        await _semaphoreSlim.WaitAsync();
        for(int count = 1; count <= 3; count++) {
            Debug.Log(count);
            await Task.Delay(1000);
        }
        _semaphoreSlim.Release();
    }