C# 从未同步的代码块调用对象同步方法。Mutex.Release() 上的异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9017521/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 05:36:39  来源:igfitidea点击:

Object synchronization method was called from an unsynchronized block of code. Exception on Mutex.Release()

c#.netmultithreadingmutex

提问by kyurkchyan

I have found different articles about this exception but none of them was my case. Here is the source code:

我找到了关于这个例外的不同文章,但没有一篇是我的情况。这是源代码:

class Program
{

    private static Mutex mutex;
    private static bool mutexIsLocked = false;
    static void Main(string[] args)
    {

        ICrmService crmService = 
            new ArmenianSoftware.Crm.Common.CrmServiceWrapper(GetCrmService("Armsoft", "crmserver"));
        //Lock mutex for concurrent access to workflow
        mutex = new Mutex(true, "ArmenianSoftware.Crm.Common.FilterCtiCallLogActivity");
        mutexIsLocked = true;

        //Create object for updating filtered cti call log
        ArmenianSoftware.Crm.Common.FilterCtiCallLog filterCtiCallLog =
            new ArmenianSoftware.Crm.Common.FilterCtiCallLog(crmService);
        //Bind events
        filterCtiCallLog.CtiCallsRetrieved += new EventHandler<ArmenianSoftware.Crm.Common.CtiCallsRetrievedEventArgs>(filterCtiCallLog_CtiCallsRetrieved);

        //Execute filter
        try
        {
            filterCtiCallLog.CreateFilteredCtiCallLogSync();
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (mutexIsLocked)
            {
                mutexIsLocked = false;
                mutex.ReleaseMutex();
            }
        }
    }

    static void filterCtiCallLog_CtiCallsRetrieved(object sender,
         ArmenianSoftware.Crm.Common.CtiCallsRetrievedEventArgs e)
    {
        tryasasas
        {
            if (mutexIsLocked)
            {
                mutexIsLocked = false;
                mutex.ReleaseMutex();
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

filterCtiCallLog.CreateFilteredCtiCallLogSync();function executes requests to server, and raises some events, one of which is CtiCallsRetrieveevent. And I need to release the mutex when this event is fired. But on calling the mutex.Release() function exception is thrown. CreateFilteredCtiCallLogSyncworks synchronously. What is the problem?

filterCtiCallLog.CreateFilteredCtiCallLogSync();函数执行对服务器的请求,并引发一些事件,其中之一是CtiCallsRetrieve事件。当这个事件被触发时,我需要释放互斥锁。但是在调用 mutex.Release() 函数时会抛出异常。 CreateFilteredCtiCallLogSync同步工作。问题是什么?

采纳答案by Hans Passant

Keeping a bool around that indicates that the mutex is owned is a grave mistake. You are not making the bool thread-safe. You got into this pickle because you are using the wrong synchronization object. A mutex has thread-affinity, the owner of a mutex is a thread. The thread that acquired it must also be the one that calls ReleaseMutex(). Which is why your code bombs.

保留一个表示拥有互斥锁的布尔值是一个严重的错误。您没有使 bool 线程安全。你进入这个泡菜是因为你使用了错误的同步对象。互斥锁具有线程亲和性,互斥锁的所有者是一个线程。获取它的线程也必须是调用 ReleaseMutex() 的线程。这就是为什么你的代码炸弹。

You in all likelihood need an eventhere, use AutoResetEvent. Create it in the main thread, call Set() in the worker, WaitOne() in the main thread to wait for the worker to complete its job. And dispose it afterwards. Also note that using a thread to perform a job and having your main thread wait for its completion is not productive. You might as well have the main thread do the job.

你很可能在这里需要一个事件,使用 AutoResetEvent。在主线程中创建,在worker中调用Set(),在主线程中调用WaitOne()等待worker完成工作。并在之后处理它。另请注意,使用线程执行作业并让主线程等待其完成是没有效率的。您也可以让主线程完成这项工作。

If you are actually doing this to protect access to an object that's not thread-safe (it isn't clear) then use the lockstatement.

如果您实际上这样做是为了保护对不是线程安全的对象的访问(不清楚),请使用lock语句。

回答by Willem van Rumpt

I only had this one once or twice, and in every case it came about by trying to release a mutex I didn't own.

我只有一两次这个,而且在每种情况下都是通过尝试释放我不拥有的互斥锁来实现的。

Are you sure the events are raised on the same thread the mutex was acquired on? Although you mention that filterCtiCallLog.CreateFilteredCtiCallLogSync()is a blocking call, perhaps it spawns of worker threads that raise the event?

您确定事件是在获取互斥锁的同一线程上引发的吗?尽管您提到这filterCtiCallLog.CreateFilteredCtiCallLogSync()是一个阻塞调用,但它可能会产生引发事件的工作线程?

回答by Martin James

Using a flag to attempt to monitor a kernel synchro object state will just not work - the point of using those synchro calls is that they work correctly without any explicit checking. Setting flags will just cause intermittent problems because the flag may be changed inappropriately due to interrupts between checking the flag and acting on it.

使用标志来尝试监视内核同步对象状态将不起作用 - 使用这些同步调用的要点是它们无需任何显式检查即可正常工作。设置标志只会导致间歇性问题,因为由于检查标志和对其采取行动之间的中断,标志可能会被不恰当地更改。

A mutex can only be released by the threat that acquired it. If you callback is called by a different thread, (one internal to CreateFilteredCtiCallLogSync() or a kernel thread pool), the release will fail.

互斥锁只能由获取它的威胁释放。如果您的回调由不同的线程(CreateFilteredCtiCallLogSync() 或内核线程池内部的一个线程)调用,则释放将失败。

It's not clear exactly what you are attempting to do. Presumably, you want to serialize access to CreateFilteredCtiCallLogSync() and the callback flags that the instance is available for re-use? If so, you could use a semaphore instead - init. it to one unit, wait for it at the start and release it in the callback.

目前尚不清楚您正在尝试做什么。想必,您想序列化对 CreateFilteredCtiCallLogSync() 的访问和实例可重用的回调标志?如果是这样,您可以改用信号量 - init。它到一个单元,在开始时等待它并在回调中释放它。

Is there some issue where sometimes the callback is not called, and hence the try/finally/release? If so this way out seems a bit dodgy if the callback is asychronous and may be called by another thread after the setup thread has left the function.

是否存在有时不调用回调的问题,从而导致 try/finally/release?如果是这样的话,如果回调是异步的并且可能在设置线程离开函数后被另一个线程调用,那么这种方法似乎有点狡猾。

回答by kyurkchyan

I have found the problem. First several things about the filterCtiCallLog class. I have designed it so to work both asynchronous and synchronous. For first I have written code for asynchronous execution. I needed a way to trigger events from child worker thread to parent, to report the working state. For this I have used AsyncOperationclass and it's post method. Here is the code part for triggering CtiCallsRetrieved event.

我找到了问题所在。首先是关于 filterCtiCallLog 类的几件事。我将它设计为可以异步和同步工作。首先,我编写了异步执行代码。我需要一种方法来触发从子工作线程到父线程的事件,以报告工作状态。为此,我使用了AsyncOperation类及其 post 方法。下面是触发 CtiCallsRetrieved 事件的代码部分。

public class FilterCtiCallLog
{
    private int RequestCount = 0;
    private AsyncOperation createCallsAsync = null;
    private SendOrPostCallback ctiCallsRetrievedPost;
    public void CreateFilteredCtiCallLogSync()
    {
        createCallsAsync = AsyncOperationManager.CreateOperation(null);
        ctiCallsRetrievedPost = new SendOrPostCallback(CtiCallsRetrievedPost);
        CreateFilteredCtiCallLog();
    }

    private void CreateFilteredCtiCallLog()
    {
        int count=0;
        //do the job
        //............
        //...........
        //Raise the event
        createCallsAsync.Post(CtiCallsRetrievedPost, new CtiCallsRetrievedEventArgs(count));
        //...........
        //...........
    }

    public event EventHandler<CtiCallsRetrievedEventArgs> CtiCallsRetrieved;

    private void CtiCallsRetrievedPost(object state)
    {
        CtiCallsRetrievedEventArgs args = state as CtiCallsRetrievedEventArgs;
        if (CtiCallsRetrieved != null)
            CtiCallsRetrieved(this, args);
    }
}

As you can see the code is executing synchronously. The problem here is in AsyncOperation.Post()method. I presumed that if it is called in the main thread it will act as simply triggering the event, not posting it to parent thread. However it wasn't the case. I don't know how it is working, but I have changed the code, to check if the CreateFilteredCtiCallLogis called sync or async. And if it is async call I used AsyncOperation.Postmethod, if not, I have simply triggered the EventHandlerif it is not null. Here is the corrected code

如您所见,代码正在同步执行。这里的问题在于AsyncOperation.Post()方法。我假设如果它在主线程中被调用,它将充当简单的触发事件,而不是将其发布到父线程。然而事实并非如此。我不知道它是如何工作的,但我已经更改了代码,以检查它是否CreateFilteredCtiCallLog被称为同步或异步。如果是异步调用,我使用了AsyncOperation.Post方法,如果不是,我只是触发了EventHandlerif it is not null。这是更正后的代码

public class FilterCtiCallLog
{
    private int RequestCount = 0;
    private AsyncOperation createCallsAsync = null;
    private SendOrPostCallback ctiCallsRetrievedPost;
    public void CreateFilteredCtiCallLogSync()
    {
        createCallsAsync = AsyncOperationManager.CreateOperation(null);
        ctiCallsRetrievedPost = new SendOrPostCallback(CtiCallsRetrievedPost);
        CreateFilteredCtiCallLog(false);
    }

    private void CreateFilteredCtiCallLog(bool isAsync)
    {
        int count=0;
        //do the job
        //............
        //...........
        //Raise the event
        RaiseEvent(CtiCallsRetrievedPost, new CtiCallsRetrievedEventArgs(count),isAsync);
        //...........
        //...........
    }

    public event EventHandler<CtiCallsRetrievedEventArgs> CtiCallsRetrieved;

    private void RaiseEvent(SendOrPostCallback callback, object state, bool isAsync)
    {
        if (isAsync)
            createCallsAsync.Post(callback, state);
        else
            callback(state);
    }

    private void CtiCallsRetrievedPost(object state)
    {
        CtiCallsRetrievedEventArgs args = state as CtiCallsRetrievedEventArgs;
        if (CtiCallsRetrieved != null)
            CtiCallsRetrieved(this, args);
    }
}

Thanks everybody for the answers!

谢谢大家的回答!

回答by Nemo

Maybe not the most meaningful error message, I've seen this happen in some third party code as below,

也许不是最有意义的错误消息,我在一些第三方代码中看到过这种情况,如下所示,

object obj = new object();
lock (obj)
{
    //do something

    Monitor.Exit(obj);//obj released

}//exception happens here, when trying to release obj

回答by Walter Verhoeven

I have seen this happen when you lock code using a Monitor, then call an async code and you get this, when using a lock(object) you get a compiler error, however between monitor.enter(object) and Monitor.Exist(object) the compiler does not complain... unfortunately.

我已经看到当您使用 Monitor 锁定代码时会发生这种情况,然后调用异步代码并得到这个,当使用 lock(object) 时,您会收到编译器错误,但是在 monitor.enter(object) 和 Monitor.Exist(object) 之间) 编译器没有抱怨......不幸的是。

回答by Igor Toropov

Another reason why this exception may occur:

可能发生此异常的另一个原因:

if (Monitor.TryEnter(_lock))
{
    try
    {
        ... await MyMethodAsync(); ...
    }
    finally
    {
        Monitor.Exit(_lock);
    }
}

I get this exception on Monitor.Exit when after 'await' another thread continues execution.

在“等待”另一个线程继续执行之后,我在 Monitor.Exit 上收到此异常。

Edit:Use SemaphoreSlim, because it doesn't require releasing thread to be the same.

编辑:使用 SemaphoreSlim,因为它不需要释放线程相同。

回答by Basalat Raja

You will also run into this exception if you do the following:

如果您执行以下操作,您也会遇到此异常:

        mutex.WaitOne();
        … Some Work...
        await someTask;
        mutex.ReleaseMutex();

That's because the code after the await can be executed on a different thread from the line just before. Basically, it seems that if you asynch code now (in early 2020), Mutexes simply don't work. Use events or something.

那是因为 await 之后的代码可以在与之前的行不同的线程上执行。基本上,如果你现在(在 2020 年初)异步代码,互斥锁根本不起作用。使用事件什么的。