Domanda

La seguente classe C # viene utilizzata in un ambiente multithread. Ho rimosso molto del codice attuale. Il problema si verifica quando si chiama MethodA e MethodB quasi contemporaneamente. L'ordine del blocco nella proprietà IsDepleted non risolve il problema. La rimozione del blocco (WaitingQueue) dalla proprietà IsDepleted risolve il deadlock, ma questa soluzione provoca un problema quando un altro thread aggiunge / rimuove un elemento da WaitingQueue tra WaitingQueue.Count == 0 e Processing.Count == 0 istruzioni.

using System.Collections.Generic;

class Example
{
    bool IsDepleted
    {
        get
        {
            lock (Processing)
            {
                lock (WaitingQueue)
        {
                    return WaitingQueue.Count == 0
             && Processing.Count == 0;
        }
            }
        }
    }

    private readonly List<object> Processing = new List<object>();
    private readonly Queue<object> WaitingQueue = new Queue<object>();

    public void MethodA(object item)
    {
        lock (WaitingQueue)
        {
            if (WaitingQueue.Count > 0)
            {
                if (StartItem(WaitingQueue.Peek()))
                {
                    WaitingQueue.Dequeue();
                }
            }
        }
    }

    public void MethodB(object identifier)
    {
        lock (Processing)
        {
            Processing.Remove(identifier);
            if (!IsDepleted)
            {
                return;
            }
        }
    //Do something...
    }

    bool StartItem(object item)
    {
        //Do something and return a value
    }
}
È stato utile?

Soluzione

Dipende se vuoi una soluzione rapida o una soluzione rigorosa.

Una soluzione rapida sarebbe solo usare un oggetto lock in tutti i casi.

es. oggetto di sola lettura privato _lock = new object ();

E poi basta bloccarlo. Tuttavia, a seconda della situazione, ciò potrebbe influire sulle prestazioni più di quanto tu possa accettare.

vale a dire. il tuo codice diventerebbe questo:

using System.Collections.Generic;

class Example
{
    private readonly object _lock = new object();

    bool IsDepleted
    {
        get
        {
            lock (_lock)
            {
                return WaitingQueue.Count == 0
                 && Processing.Count == 0;
            }
        }
    }

    private readonly List<object> Processing = new List<object>();
    private readonly Queue<object> WaitingQueue = new Queue<object>();

    public void MethodA(object item)
    {
        lock (_lock)
        {
            if (WaitingQueue.Count > 0)
            {
                if (StartItem(WaitingQueue.Peek()))
                {
                    WaitingQueue.Dequeue();
                }
            }
        }
    }

    public void MethodB(object identifier)
    {
        lock (_lock)
        {
            Processing.Remove(identifier);
            if (!IsDepleted)
            {
                return;
            }
        }
        //Do something...
    }

    bool StartItem(object item)
    {
        //Do something and return a value
    }
}

Altri suggerimenti

Prendi il blocco Processing nel metodo A e il blocco WaitingQueue nel metodo B (in altre parole, assomigli al primo blocco di codice). In questo modo, prendi sempre i lucchetti nello stesso ordine e non ti fermerai mai.

Semplifica il tuo codice e usa solo un singolo oggetto per bloccare. Puoi anche sostituire i tuoi lucchetti con:

Monitor.TryEnter (Processing, 1000)

questo ti darà un timeout di 1 secondo. Quindi essenzialmente:

        if (Monitor.TryEnter(Processing, 1000))
        {
            try
            {
                //do x
            }
            finally
            {
                Monitor.Exit(Processing);
            }
        }

Ora non si fermerà il deadlock ma è possibile gestire il caso in cui non si ottiene un blocco.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top