web-dev-qa-db-fra.com

La méthode de synchronisation d'objet a été appelée à partir d'un bloc de code non synchronisé. Exception sur Mutex.Release ()

J'ai trouvé différents articles sur cette exception, mais aucun d'entre eux ne concernait mon cas… .. Voici le code source:

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

La fonction filterCtiCallLog.CreateFilteredCtiCallLogSync(); exécute les demandes au serveur et déclenche certains événements, dont l'un est l'événement CtiCallsRetrieve. Et je dois libérer le mutex quand cet événement est déclenché. Mais lors de l'appel de la fonction mutex.Release (), une exception est levée. CreateFilteredCtiCallLogSync fonctionne de manière synchrone. Quel est le problème?

21
kyurkchyan

Garder un bool autour qui indique que le mutex est possédé est une erreur grave. Vous ne faites pas le bool thread-safe. Vous êtes entré dans ce pickle parce que vous utilisez le mauvais objet de synchronisation. Un mutex a une affinité de thread, le propriétaire d'un mutex est un thread. Le thread qui l'a acquis doit également être celui qui appelle ReleaseMutex (). C'est pourquoi votre code bombes.

Vous avez probablement besoin d'un événement ici, utilisez AutoResetEvent. Créez-le dans le thread principal, appelez Set () dans le worker, WaitOne () dans le thread principal pour attendre que le worker termine son travail. Et en disposer ensuite. Notez également qu'utiliser un thread pour effectuer un travail et laisser votre thread principal attendre son achèvement n'est pas productif. Vous pourriez aussi bien avoir le fil principal faire le travail.

Si vous faites cela pour protéger l'accès à un objet qui n'est pas thread-safe (ce n'est pas clair), utilisez l'instruction lock.

38
Hans Passant

J'ai trouvé le problème. Tout d'abord, plusieurs choses à propos de la classe filterCtiCallLog. Je l'ai conçu pour fonctionner de manière asynchrone et synchrone. Pour la première fois, j'ai écrit du code pour une exécution asynchrone. J'avais besoin d'un moyen de déclencher des événements d'un thread de travail enfant vers un parent afin de signaler l'état de fonctionnement. Pour cela, j'ai utilisé AsyncOperation class et sa méthode post. Voici la partie code pour le déclenchement de l'événement 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);
    }
}

Comme vous pouvez le constater, le code s’exécute de manière synchrone. Le problème ici est dans la méthode AsyncOperation.Post(). J'ai supposé que s'il était appelé dans le fil principal, il agirait simplement comme déclencheur de l'événement, et non comme message dans le fil parent. Cependant ce n'était pas le cas. Je ne sais pas comment cela fonctionne, mais j'ai changé le code, pour vérifier si la CreateFilteredCtiCallLog s'appelle sync ou async. Et s’il s’agit d’un appel asynchrone j’ai utilisé la méthode AsyncOperation.Post, sinon, j’ai simplement déclenché la EventHandler s’il ne s’agit pas de null. Voici le code corrigé

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

Merci à tous pour les réponses!

5
kyurkchyan

Une autre raison pour laquelle cette exception peut se produire:

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

Je reçois cette exception sur Monitor.Exit après "attendre" qu'un autre thread continue l'exécution.

2
Igor Toropov

Je n'ai eu celui-ci qu'une ou deux fois, et dans tous les cas, c'est en essayant de libérer un mutex que je ne possédais pas.

Êtes-vous sûr que les événements sont générés sur le même fil que le mutex a été acquis? Bien que vous mentionniez que filterCtiCallLog.CreateFilteredCtiCallLogSync() est un appel bloquant, peut-être qu'il génère des threads de travail qui déclenchent l'événement?

2
Willem van Rumpt

L'utilisation d'un indicateur pour tenter de surveiller l'état d'un objet synchro dans le noyau ne fonctionnera tout simplement pas. L'intérêt d'utiliser ces appels synchro est qu'ils fonctionnent correctement sans vérification explicite. Le paramétrage des indicateurs ne provoquera que des problèmes intermittents car l'indicateur peut être modifié de manière inappropriée en raison d'interruptions entre la vérification de l'indicateur et son action. 

Un mutex ne peut être libéré que par la menace qui l'a acquis. Si votre rappel est appelé par un autre thread (un interne à CreateFilteredCtiCallLogSync () ou un pool de threads du noyau), la publication échouera.

Ce que vous essayez de faire n'est pas clair. Vraisemblablement, vous voulez sérialiser l'accès à CreateFilteredCtiCallLogSync () et aux indicateurs de rappel indiquant que l'instance est disponible pour une réutilisation? Si c'est le cas, vous pouvez utiliser un sémaphore à la place, init. à une unité, attendez-la au début et relâchez-la dans le rappel.

Existe-t-il un problème où parfois le rappel n’est pas appelé, d’où la tentative/enfin/libération? Si c'est le cas, cette sortie semble un peu risquée si le rappel est asynchrone et peut être appelé par un autre thread après que le thread d'installation a quitté la fonction.

2
Martin James

J'ai vu cela se produire lorsque vous verrouillez le code à l'aide d'un moniteur, puis appelez un code asynchrone et que vous obtenez cela, lorsque vous utilisez un verrou (objet), vous obtenez une erreur de compilateur, cependant entre monitor.enter (objet) et Monitor.Exist (objet ) le compilateur ne se plaint pas ... malheureusement.

1
Walter Verhoeven

Peut-être pas le message d'erreur le plus significatif, j'ai vu cela se produire dans un code tiers comme ci-dessous,

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

    Monitor.Exit(obj);//obj released

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