wake-up-neo.com

Die Objektsynchronisationsmethode wurde aus einem nicht synchronisierten Codeblock aufgerufen. Ausnahme bei Mutex.Release ()

Ich habe verschiedene Artikel zu dieser Ausnahme gefunden, aber keiner davon war mein Fall ... Hier ist der Quellcode:

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;
        }
    }
}

Die Funktion filterCtiCallLog.CreateFilteredCtiCallLogSync(); führt Anforderungen an den Server aus und löst einige Ereignisse aus, von denen eines CtiCallsRetrieve-Ereignis ist. Und ich muss den Mutex freigeben, wenn dieses Ereignis ausgelöst wird. Beim Aufruf der Funktion mutex.Release () wird jedoch eine Ausnahme ausgelöst. CreateFilteredCtiCallLogSync arbeitet synchron. Worin besteht das Problem?

21
kyurkchyan

Wenn Sie einen Bool in der Nähe halten, der besagt, dass der Mutex im Besitz ist, ist dies ein schwerwiegender Fehler. Sie machen den bool nicht fadensicher. Sie sind in diese Pickle geraten, weil Sie das falsche Synchronisationsobjekt verwenden. Ein Mutex hat eine Thread-Affinität, der Besitzer eines Mutex ist ein Thread. Der Thread, der es erworben hat, muss auch derjenige sein, der ReleaseMutex () aufruft. Deshalb ist Ihr Code Bomben.

Sie benötigen höchstwahrscheinlich ein event hier verwenden Sie AutoResetEvent. Erstellen Sie es im Haupt-Thread, rufen Sie Set () im Worker auf, WaitOne () im Haupt-Thread, um zu warten, bis der Worker seinen Job abgeschlossen hat. Und entsorgen Sie es danach. Beachten Sie auch, dass die Verwendung eines Threads zum Ausführen eines Jobs und das Warten auf den Abschluss Ihres Haupt-Threads nicht produktiv ist. Sie könnten genauso gut den Haupt-Thread erledigen lassen.

Wenn Sie dies tatsächlich tun, um den Zugriff auf ein Objekt zu schützen, das nicht threadsicher ist (es ist nicht eindeutig), verwenden Sie die Anweisung lock .

38
Hans Passant

Ich habe das Problem gefunden. Zuerst einige Dinge über die filterCtiCallLog-Klasse. Ich habe es so entworfen, dass es sowohl asynchron als auch synchron arbeitet. Zunächst habe ich Code für die asynchrone Ausführung geschrieben. Ich brauchte eine Möglichkeit, Ereignisse von einem untergeordneten Worker-Thread zu einem übergeordneten Element auszulösen, um den Arbeitsstatus zu melden. Dafür habe ich AsyncOperation class und deren Post-Methode verwendet. Hier ist der Codeteil zum Auslösen des CtiCallsRetrieved-Ereignisses.

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);
    }
}

Wie Sie sehen, wird der Code synchron ausgeführt. Das Problem liegt hier in der AsyncOperation.Post()-Methode. Ich nahm an, dass es, wenn es im Haupt-Thread aufgerufen wird, einfach das Ereignis auslöst und nicht im übergeordneten Thread veröffentlicht. Dies war jedoch nicht der Fall. Ich weiß nicht, wie es funktioniert, aber ich habe den Code geändert, um zu prüfen, ob die Variable CreateFilteredCtiCallLog sync oder async heißt. Und wenn es sich um einen asynchronen Aufruf handelt, habe ich die AsyncOperation.Post-Methode verwendet. Wenn nicht, habe ich einfach die EventHandler ausgelöst, wenn es nicht null ist. Hier ist der korrigierte Code

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);
    }
}

Danke an alle für die Antworten!

5
kyurkchyan

Ein weiterer Grund, warum diese Ausnahme auftreten kann:

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

Ich bekomme diese Ausnahme auf Monitor.Exit, wenn nach 'waitit' ein anderer Thread die Ausführung fortsetzt.

2
Igor Toropov

Ich hatte dieses nur ein oder zweimal, und in jedem Fall kam es zum Versuch, einen Mutex freizugeben, den ich nicht besaß.

Sind Sie sicher, dass die Ereignisse in demselben Thread ausgelöst werden, in dem der Mutex erfasst wurde? Obwohl Sie erwähnen, dass filterCtiCallLog.CreateFilteredCtiCallLogSync() ein blockierender Aufruf ist, kann es sein, dass Worker-Threads entstehen, die das Ereignis auslösen.

2

Die Verwendung eines Flags zum Überwachen des Status eines Kernel-Synchronisationsobjekts funktioniert einfach nicht. Die Verwendung dieser Synchronisationsaufrufe besteht darin, dass sie ohne explizite Überprüfung ordnungsgemäß funktionieren. Das Setzen von Flags führt nur zu zeitweiligen Problemen, da das Flag aufgrund von Interrupts zwischen dem Prüfen des Flags und dem Einwirken auf das Flag unpassend geändert werden kann. 

Ein Mutex kann nur von der Bedrohung freigegeben werden, die ihn erworben hat. Wenn Ihr Rückruf von einem anderen Thread aufgerufen wird (einer von CreateFilteredCtiCallLogSync () oder einem Kernel-Thread-Pool intern), schlägt die Veröffentlichung fehl.

Es ist nicht genau klar, was Sie zu tun versuchen. Vermutlich möchten Sie den Zugriff auf CreateFilteredCtiCallLogSync () und die Callback-Flags serialisieren, die für die Wiederverwendung der Instanz verfügbar sind. Wenn ja, können Sie stattdessen ein Semaphor verwenden - init. Warten Sie beim Start darauf und lassen Sie es im Callback los.

Gibt es ein Problem, bei dem der Callback manchmal nicht aufgerufen wird und daher Try/finally/Release? Wenn ja, so erscheint dieser Ausweg etwas zweifelhaft, wenn der Rückruf asynchron ist und möglicherweise von einem anderen Thread aufgerufen wird, nachdem der Setup-Thread die Funktion verlassen hat.

2
Martin James

Ich habe gesehen, wie dies passiert, wenn Sie Code mit einem Monitor sperren und dann einen asynchronen Code aufrufen. Wenn Sie eine Sperre (Objekt) verwenden, erhalten Sie einen Compiler-Fehler, jedoch zwischen monitor.enter (Objekt) und Monitor.Exist (Objekt) ) der Compiler beschwert sich nicht ... leider.

1

Vielleicht nicht die aussagekräftigste Fehlermeldung. Ich habe dies in einem Code von Drittanbietern wie folgt beobachtet

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

    Monitor.Exit(obj);//obj released

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