Skip to main content
 首页 » 编程设计

c#之用 IDisposable 包装互斥锁并测试它,但测试永远不会结束

2024年01月26日30arxive

我正在尝试用 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); 
        }             
    } 
}