.NET FrameworkのMutexの納得いかない仕様
変な仕様
.NET Frameworkの名前付きMutexは、どうも、Disposeというか、Closeしても、MutexをReleaseMutexしてくれないような感じ。試しに下のようなコードを書いた。
using System; using System.Threading; namespace MutexTest { public class MutexTest { public static void Main() { using (Mutex m = new Mutex(false, "Hello, world")) { m.WaitOne(); Console.WriteLine("Hey, I'm in the protected world!"); Thread.Sleep(10000); Console.WriteLine("..."); // 本当は次の行が必要 // m.ReleaseMutex(); } } } }
このプログラムを、1秒ぐらいの時間差をおいて、2つのプロセスを起動(実行)すると、1つめのプロセスは正常に終了するのだが、2つめのプロセスは、
ハンドルされていない例外: System.Threading.AbandonedMutexException: 放棄されたミューテックスのため、待機は完了しました。 場所 System.Threading.WaitHandle.WaitOne(Int64 timeout, Boolean exitContext) 場所 MutexTest.MutexTest.Main()
といったエラーを出力して終了する。とはいえ、Mutexのカーネルオブジェクト自体は正常に破棄されるようで、この後にもう一度、このプログラムを実行しても、問題はない。
つまり、正常に動作するようにするには、ReleaseMutexを明示的に呼び出すしかないらしい。普通に考えて、ReleaseMutexせずにDisposeするなんて考えにくいのだけど、ふと思って、次みたいなコードを書いてみた。
using System; using System.Threading; namespace MutexTest { public class MutexTest { public static void Main() { using (Mutex m = new Mutex(false, "Hello, world")) { m.WaitOne(); Console.WriteLine("Hey, I'm in the protected world!"); Thread.Sleep(10000); Console.WriteLine("..."); } using (Mutex m = new Mutex(false, "Hello, world")) { m.ReleaseMutex(); } } } }
全くナンセンスなコードなんだけど、このコードでは、上記のAbandonedMutexExceptionは出なくなる。つまり、正しくReleaseMutexすることには成功しているっぽい。
ただし、この場合、2つ目のプロセスでは、
ハンドルされていない例外: System.ApplicationException: オブジェクト同期メソッドは、コードの非同期ブロックから呼び出されました。 場所 System.Threading.Mutex.ReleaseMutex() 場所 MutexTest.MutexTest.Main()
というよくわからない例外が出る。なんだろう、これは。
安全なMutex
ということで、次のようなラッパークラスを作った。
class SessionLock : IDisposable { public SessionLock(string mutexName) { m_mutex = new Mutex(false, mutexName); m_mutex.WaitOne(); } public void Dispose() { try { m_mutex.ReleaseMutex(); m_mutex.Close(); } catch { } } Mutex m_mutex; }
これならば、
using System; using System.Threading; namespace MutexTest { ... public class MutexTest { public static void Main() { using (new SessionLock("Hello, world")) { Console.WriteLine("Hey, I'm in the protected world!"); Thread.Sleep(10000); Console.WriteLine("..."); } } } }
というRAIIっぽいコンパクトなコードが書ける。というか、C#のusingは、newの結果を変数に代入しなくてもいいんですねぇ。