Pytanie Metoda synchronizacji obiektu została wywołana z niezsynchronizowanego bloku kodu. Wyjątek na Mutex.Release ()


Znalazłem różne artykuły na temat tego wyjątku, ale żaden z nich nie był moim przypadkiem. Oto kod źródłowy:

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(); funkcja wykonuje żądania do serwera i podnosi pewne zdarzenia, z których jedna jest CtiCallsRetrieve zdarzenie. I muszę zwolnić muteks po uruchomieniu tego zdarzenia. Ale wywołanie wyjątku funkcji mutex.Release () jest generowane. CreateFilteredCtiCallLogSync działa synchronicznie. Jaki jest problem?


19
2018-01-26 11:31


pochodzenie




Odpowiedzi:


Utrzymanie bool wokół, które wskazuje, że mutex jest własnością jest poważnym błędem. Nie sprawiasz, że bool jest bezpieczny dla wątków. Dostałeś się do tego pikla, ponieważ używasz niewłaściwego obiektu synchronizacji. Muteks ma powinowactwo do wątku, właściciel muteksu jest wątkiem. Wątek, który ją uzyskał, musi być również tym, który wywołuje funkcję ReleaseMutex (). Właśnie dlatego twój kod bomby.

Ty najprawdopodobniej potrzebujesz zdarzenie tutaj, użyj AutoResetEvent. Utwórz go w głównym wątku, wywołaj funkcję Set () w module roboczym, WaitOne () w głównym wątku, aby czekać na zakończenie pracy przez pracownika. I wyrzuć to później. Należy również pamiętać, że używanie wątku do wykonania zadania i oczekiwanie na zakończenie głównego wątku nie jest produktywne. Równie dobrze możesz wykonać główny wątek.

Jeśli faktycznie robisz to, aby chronić dostęp do obiektu, który nie jest bezpieczny dla wątków (to nie jest jasne), użyj zamek komunikat.


36
2018-01-26 11:50



Masz rację - przegapiłem, że wątek konfigurujący wywołanie zwrotne jest głównym wątkiem :( - Martin James
AutoResetEvent jest świetny! Dzięki Hans. - Gabe Halsmer


Znalazłem problem. Najpierw kilka rzeczy o klasie filterCtiCallLog. Zaprojektowałem go tak, aby działał zarówno asynchronicznie, jak i synchronicznie. Najpierw napisałem kod do wykonania asynchronicznego. Potrzebowałem sposobu wyzwalania zdarzeń od wątku pracownika podrzędnego do rodzica, aby zgłosić stan roboczy. Do tego użyłem AsyncOperation klasa i metoda postu. Oto część kodu do wyzwalania zdarzenia 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);
    }
}

Jak widać kod wykonuje się synchronicznie. Problem tutaj jest w AsyncOperation.Post() metoda. Przypuszczałem, że jeśli zostanie wywołany w głównym wątku, będzie działał jako zwykłe wywołanie zdarzenia, a nie publikowanie go w wątku nadrzędnym. Jednak tak nie było. Nie wiem jak to działa, ale zmieniłem kod, aby sprawdzić, czy CreateFilteredCtiCallLog nazywa się sync lub async. A jeśli jest to połączenie asynchroniczne, którego użyłem AsyncOperation.Post metoda, jeśli nie, po prostu wywołałem EventHandler Jeżeli nie jest null. Oto poprawiony kod

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

Dziękuję wszystkim za odpowiedzi!


5
2018-01-30 04:53





Miałem ten jeden raz lub dwa razy, aw każdym przypadku doszło do tego, próbując uwolnić muteks, którego nie posiadałem.

Czy jesteś pewien, że wydarzenia są poruszane w tym samym wątku, na którym został zdobyty muteks? Chociaż o tym wspomniałeś filterCtiCallLog.CreateFilteredCtiCallLogSync() jest blokującym wywołaniem, może spawns wątków roboczych, które podnoszą wydarzenie?


2
2018-01-26 11:47





Używanie flagi do monitorowania stanu obiektu synchro jądra po prostu nie działa - punktem korzystania z tych wywołań synchro jest to, że działają poprawnie bez wyraźnego sprawdzania. Ustawienie flag spowoduje tylko sporadyczne problemy, ponieważ flaga może zostać niewłaściwie zmieniona z powodu przerw między sprawdzaniem flagi a działaniem na niej.

Muteks może zostać zwolniony tylko przez zagrożenie, które go nabyło. Jeśli wywołanie zwrotne jest wywoływane przez inny wątek (jeden wewnętrzny do CreateFilteredCtiCallLogSync () lub puli wątków jądra), wydanie nie powiedzie się.

Nie jest jasne, co dokładnie próbujesz zrobić. Prawdopodobnie chcesz serializować dostęp do CreateFilteredCtiCallLogSync () i flag wywołań zwrotnych, które instancja jest dostępna do ponownego użycia? Jeśli tak, możesz użyć semafora zamiast - init. do jednej jednostki, poczekaj na nią na początku i zwolnij ją w oddzwanianiu.

Czy jest jakiś problem, w którym czasami wywołanie zwrotne nie jest wywoływane, a zatem próba / finally / release? Jeśli tak, to wyjście wydaje się nieco podejrzane, jeśli wywołanie zwrotne jest asychroniczne i może zostać wywołane przez inny wątek po tym, jak wątek konfiguracji opuścił funkcję.


2
2018-01-26 12:00





Może nie najbardziej znaczący komunikat o błędzie, widziałem to w jakimś kodzie strony trzeciej, jak poniżej,

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

    Monitor.Exit(obj);//obj released

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

0
2017-11-17 20:07





Widziałem, jak to się stało, gdy blokujesz kod za pomocą monitora, a następnie wywołujesz kod asynchroniczny i otrzymujesz to, gdy używasz blokady (obiektu), otrzymujesz błąd kompilatora, jednak między monit.enter (obiekt) i Monitor.Exist (obiekt ) kompilator nie narzeka ... niestety.


0
2018-02-01 15:49