技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

C#:Mutexでの排他制御

2020/04/18 追記
実務でコピペするとAbandonedMutexExceptionが発生し得る若干行儀の悪いサンプルコードだったので、書き直しました。
 
-----------------------
 
排他制御の方法の一つとして、C#にはMutexと呼ばれる機能が用意されています。
何れか一つのスレッドがMutexによるロックを取得することができます。
他のスレッドによりロックが取得されている場合の処理を別途記述すれば、この機能を使用して排他制御が可能となります。
 
以下、サンプルコードです。
 
【サンプルコード】

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MutexTest
{
    class Program
    {
        static void Main(string[ ] args)
        {
            using (Mutex mutex = new Mutex(false)) // 使い終わったらスレッド解放
            {
                const int N = 3;
                // id0…0.7秒目~1.7秒目に掴む
                // id1…1.4秒目から待機、1.7~2.7秒目に掴む
                // id2…2.1秒目から待機、2.6秒目に諦める
                Parallel.For(0, N, id => // id0~2のスレッドを生成
                {
                    Thread.Sleep(id * 700);
                    // ロック取得、成功ならtrue
                    // 第一引数は待ち時間
                    if (mutex.WaitOne(500, false))
                    {
                        try
                        {
                            Console.WriteLine("ID" + id + "が掴みました。");
                            Thread.Sleep(1000);
                        }
                        finally
                        {
                            // Mutex資源解放
                            // 忘れてCloseすると待機待ち側でAbandonedMutexException
                            // Disposeでは実行されないためusingに頼るのは不可
                            mutex.ReleaseMutex();
                            Console.WriteLine("ID" + id + "が解放しました。");
                        }
                    }
                    else
                    {
                        Console.WriteLine("ID" + id + "は掴めませんでした。");
                    }
                });
            }
            Console.ReadKey();
        }
    }
}
 
【実行結果】
ID0が掴みました。
ID0が解放しました。
ID1が掴みました。
ID2は掴めませんでした。
ID1が解放しました。
 
--------------------------------
 
また、Mutexには、名前を付けることができます。
名前を付けることで、プロセス間でも排他制御が可能となります。
(ただし、意図せずMutex名が被ると意図しないロックがかかってしまうため、扱いには注意が必要です)
 
【サンプルコード】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MutexTest2
{
    class Program
    {
        static void Main(string[ ] args)
        {
            // 名前付きMutexだとプロセス間で共有できる
            //using (Mutex mutex = new Mutex(false))
            using (Mutex mutex = new Mutex(false,"hoge"))
            {
                // プロセス名取得(表示用)
                System.Diagnostics.Process p
                    = System.Diagnostics.Process.GetCurrentProcess();

                Console.WriteLine(DateTime.Now);
                Console.WriteLine(p.Id + "が掴もうとしています。");
                if (mutex.WaitOne(5000, false))
                {
                    try
                    {
                        Console.WriteLine(DateTime.Now);
                        Console.WriteLine(p.Id + "が掴みました。");
                        Thread.Sleep(10000);
                    }
                    finally
                    {
                        mutex.ReleaseMutex();
                        Console.WriteLine(DateTime.Now);
                        Console.WriteLine(p.Id + "が解放しました。");
                    }
                }
                else
                {
                    Console.WriteLine(DateTime.Now);
                    Console.WriteLine(p.Id + "は掴めませんでした。");
                }
            }
            Console.ReadKey();
        }
    }
}
 
【実行結果】
※exeファイルを3回起動した結果。
 1つ目のプロセス…掴んで解放
 2つ目のプロセス…解放を待って掴んで解放
 3つ目のプロセス…解放を待って掴めず諦める

2020/04/18 7:01:44
10420が掴もうとしています。
2020/04/18 7:01:44
10420が掴みました。
2020/04/18 7:01:54
10420が解放しました。

2020/04/18 7:01:50
2416が掴もうとしています。
2020/04/18 7:01:54
2416が掴みました。
2020/04/18 7:02:04
2416が解放しました。

2020/04/18 7:01:56
7524が掴もうとしています。
2020/04/18 7:02:01
7524は掴めませんでした。