我正在尝试用 IDisposable 类包装一个 Mutex,如下所示:
public class NamedMutex : IDisposable
{
private static readonly object _syncLock = new object();
private readonly Mutex _namedMutex;
private readonly bool _createdNew;
public NamedMutex(string name)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
//lock (_syncLock)
{
_namedMutex = new Mutex(initiallyOwned: false, name: name, createdNew: out _createdNew);
}
_namedMutex.WaitOne();
}
public void Dispose()
{
//lock (_syncLock)
{
//if (_createdNew)
_namedMutex.ReleaseMutex();
_namedMutex.Dispose();
}
}
}
正如您从注释掉的代码中看到的那样,我已经尝试了几乎所有我能想到的方法来使其正常工作,但要么是我的测试有误,要么是上面的实现有问题,因为测试永远不会结束(可能是我无法识别的死锁或它因未同步异常而崩溃)。
这是我为 LINQPad 改编的测试:
void Main()
{
var sw = Stopwatch.StartNew();
var task1 = Task.Run(async () =>
{
using (new NamedMutex("foo"))
{
Console.WriteLine(3);
await Task.Delay(TimeSpan.FromSeconds(3));
}
});
var task2 = Task.Run(async () =>
{
using (new NamedMutex("foo"))
{
Console.WriteLine(2);
await Task.Delay(TimeSpan.FromSeconds(2));
}
});
Task.WaitAll(task1, task2);
//Assert.IsTrue(sw.Elapsed.TotalSeconds >= 5);
sw.Elapsed.Dump(); // LINQPad
}
请您参考如下方法:
这是因为 await。在您的 await Task.Delay(..) 之后,您可能不再处于与 await 语句之前相同的线程中。因此,在某些情况下,您试图从不拥有它的线程中释放您的互斥量 - 因此您的问题。这很容易通过在 await 前后写入当前线程来验证:
class Program {
public static void Main() {
while (true) {
var sw = Stopwatch.StartNew();
var task1 = Task.Run(async () => {
using (new NamedMutex("foo")) {
Console.WriteLine("first before await: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(TimeSpan.FromSeconds(2));
Console.WriteLine("first after await: " + Thread.CurrentThread.ManagedThreadId);
}
});
var task2 = Task.Run(async () => {
using (new NamedMutex("foo")) {
Console.WriteLine("second before await: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine("second after await: " + Thread.CurrentThread.ManagedThreadId);
}
});
Task.WaitAll(task1, task2);
//Assert.IsTrue(sw.Elapsed.TotalSeconds >= 5);
Console.WriteLine(sw.Elapsed);
}
}
}
