ReleaseMutex:从未同步的代码块中调用了对象同步方法

15

我有这样一段非常简单的代码,很少会在调用ReleaseMutex()时抛出"System.ApplicationException: Object synchronization method was called from an unsynchronized block of code."。

我逻辑分析了该方法的流程,但无法理解为什么会发生这种情况。据我理解,这种情况下互斥锁的所有权是保证的:

    readonly string mutexKey;

    public Logger(string dbServer, string dbName)
    {
        this.mutexKey = ServiceManagerHelper.GetServiceName(dbServer, dbName);
    }

    private void Log(LogType type, string message, Exception ex)
    {
        using (var mutex = new Mutex(false, mutexKey))
        {
            bool acquiredMutex;
            try
            {
                acquiredMutex = mutex.WaitOne(TimeSpan.FromSeconds(5));
            }
            catch (AbandonedMutexException)
            {
                acquiredMutex = true;
            }

            if (acquiredMutex)
            {
                try
                {

                    // some application code here

                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            }
        }
    }
3个回答

13
        catch (AbandonedMutexException)
        {
            acquiredMutex = true;
        }

你的代码存在一个非常严重的 bug,捕获 AbandonedMutexException 异常是不正确的,这是一个非常严重的失误。另一个线程获得了 mutex,但在没有调用 ReleaseMutex() 的情况下终止了。你已经永久性地失去了同步,mutex 不再可用。

你犯了错误并假设已经获取了 mutex,结果有些幸运。现在,ReleaseMutex() 调用将以你引用的异常失败。

你无法从这个问题中恢复,除非终止程序(明智的选择)或完全禁用日志记录,使 mutex 永远不会被使用。通过移除 catch 子句来做出明智的选择。发现问题的真正源头——崩溃并未调用 ReleaseMutex() 的线程——超出了此问题的范围,没有提示。你一直忽视着这个问题,通过捕获 AME 掩盖了它,但你不能忽视它。


2
嗨,汉斯,谢谢回复。虽然我还是不明白...MSDN说:“当一个线程放弃互斥锁时,在下一个获取互斥锁的线程中抛出异常。”所以如果我们处理这个异常并验证受保护实体的正确性-我们就没问题了。对吧?如果不是,为什么?此外,来自MSDN的信息:“请求所有权的下一个线程可以处理此异常并继续进行,前提是可以验证数据结构的完整性。”和:https://dev59.com/7GUp5IYBdhLWcg3wLVOn - Igor Malin
2
除非你能证明雪橇真的可以在空中飞行,否则没有圣诞老人存在。这确实需要让那辆雪橇先飞起来。编写一个故意放弃互斥锁并保持程序运行的测试。 - Hans Passant

9
在我的情况下,我看到了与Nathan Schubkegel相同的行为。我使用await,而Thread.CurrentThread.ManagedThreadId会给出另一个值,对于“相同”的线程。我的意思是,线程是使用ManagedThreadId == 10启动的,并且Mutex拥有此线程ID,但稍后ReleaseMutex()会导致带有消息的ApplicationException:“从未同步的代码块中调用了对象同步方法”,并且此时我看到ManagedThreadId == 11 :)。似乎await有时会在返回时更改线程ID。这似乎就是原因。Mutex认为另一个线程想要释放它。很遗憾,Mutex文档没有在这一点上做出注意

因此,在Mutex获取和释放之间,您不能使用异步运算符await。这是因为C#编译器通过异步回调替换普通运算符await,并且此回调可以由另一个线程进行。通常,它是同一个线程,但有时它是来自线程池的另一个线程。

Mutex检查线程。只有获取Mutex的线程才能释放它。如果您需要在不进行此检查的情况下进行同步,请使用SemaphoreSemaphoreSlim具有异步方法WaitAsync() - 很酷。


4
当你在一个没有拥有互斥锁的线程中调用ReleaseMutex()时,会引发此异常。请查找释放互斥锁的代码// some application code here
同时,请确认你是否在调用WaitOne()的同一线程中调用了ReleaseMutex()。例如:我遇到这个问题是因为我在使用async/await时,我的代码在不同的线程上恢复并尝试释放一个线程没有拥有的互斥锁。

这是一个命名互斥量,所以我认为你在这里是不正确的。 - debater
因为我正在使用async/await,我的代码在不同的线程上恢复并尝试释放一个线程没有拥有的互斥锁 - 当您使用await xx.ConfigureAwait(false)时会出现这种情况。对于命名互斥体也会抛出此异常。 - Dilyan Rusev
这个答案是正确的。拥有 Mutex 的线程通常不会是在等待异步代码完成后恢复执行的同一个线程。 - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接