用IDisposable包装Mutex并测试它,但测试永远不会结束

t3c*_*b0t 1 c# mutex idisposable thread-safety

我试图Mutex用这样的IDisposable类包装一个:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你从注释掉的代码中看到的那样,我已经尝试了几乎所有我能想到的工作,但要么是我的测试错误,要么是上述实现不对,因为测试要么永远不会结束(可能是死锁我无法识别或与未同步的异常崩溃).

这是我为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
}
Run Code Online (Sandbox Code Playgroud)

Evk*_*Evk 5

这是因为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);
        }            
    }
}
Run Code Online (Sandbox Code Playgroud)