Domanda

Ho sviluppato una coda di produttore-consumatore generico che pulsa dal monitor nel seguente modo:

l'Enqueue:

    public void EnqueueTask(T task)
    {
        _workerQueue.Enqueue(task);
        Monitor.Pulse(_locker);
    }

la dequeue:

private T Dequeue()
    {
        T dequeueItem;
        if (_workerQueue.Count > 0)
        {
               _workerQueue.TryDequeue(out dequeueItem);
            if(dequeueItem!=null)
                return dequeueItem;
        }
        while (_workerQueue.Count == 0)
            {
                Monitor.Wait(_locker);
        }
         _workerQueue.TryDequeue(out dequeueItem);
        return dequeueItem;
    }

la sezione di attesa produce i seguenti SynchronizationLockException: "Metodo di sincronizzazione oggetto è stato chiamato da un blocco sincronizzato di codice" non ho bisogno di synch esso? perché ? E 'meglio usare ManualResetEvents o la versione Slim di .NET 4.0?

È stato utile?

Soluzione

Sì, le attuali esigenze di thread per "possedere" il monitor per chiamare sia Wait o Pulse, come documentato. (Quindi, avrete bisogno di serratura per Pulse pure.) Non conosco i dettagli per questo che è necessario, ma è lo stesso in Java. Ho trovato di solito avrei voluto farlo comunque, però, per rendere il codice chiamando pulita.

Si noti che Wait rilascia il monitor stesso, quindi attende la Pulse, poi riacquista il monitor prima di tornare.

Come per l'utilizzo ManualResetEvent o AutoResetEvent invece -. Si potrebbe, ma personalmente preferisco utilizzando i metodi Monitor a meno che non ho bisogno di alcune delle altre caratteristiche di maniglie di attesa (come atomicamente attesa di uno / tutti multipli maniglie)

Altri suggerimenti

Dalla descrizione MSDN di Monitor.Wait ():

  

rilascia il blocco su un oggetto e blocca il thread corrente fino riacquisisce il blocco.

I 'rilascia il lock' parte è il problema, l'oggetto non viene bloccato. Si sta trattando l'oggetto _locker come se si tratta di un WaitHandle. Fare il proprio disegno di blocco che è dimostrabilmente corretto è una forma di magia nera che è meglio lasciare al nostro uomo di medicina, Jeffrey Richter e Joe Duffy. Ma io darò questo un colpo:

public class BlockingQueue<T> {
    private Queue<T> queue = new Queue<T>();

    public void Enqueue(T obj) {
        lock (queue) {
            queue.Enqueue(obj);
            Monitor.Pulse(queue);
        }
    }

    public T Dequeue() {
        T obj;
        lock (queue) {
            while (queue.Count == 0) {
                Monitor.Wait(queue);
            }
            obj = queue.Dequeue();
        }
        return obj;
    }
}

Nella maggior parte qualsiasi pratica scenario produttore / consumatore si vorrà strozzare il produttore in modo che non può riempire la coda illimitata. Controllare di Duffy BoundedBuffer disegno per un esempio. Se potete permettervi di passare a .NET 4.0 allora sicuramente vuole approfittare della sua classe ConcurrentQueue, ha un sacco di magia più nera con basso overhead di bloccaggio e di spin-attesa.

Il modo corretto di vista Monitor.Wait e Monitor.Pulse / PulseAll non è fornire un mezzo di attesa, ma piuttosto (per Wait) come mezzo di far conoscere al sistema che il codice è in un ciclo di attesa che non può uscire fino qualcosa di cambiamenti di interesse, e (per Pulse / PulseAll) come un mezzo di far conoscere al sistema che il codice ha appena cambiato qualcosa che potrebbe causare soddisfare la condizione di uscita ciclo di attesa di qualche altro thread. Uno dovrebbe essere in grado di sostituire tutte le occorrenze di Wait con Sleep(0) e avere ancora il lavoro codice correttamente (anche se molto meno efficiente, a seguito di spendere tempo di CPU più volte le condizioni che non sono cambiati Testing).

Per questo meccanismo di lavoro, è necessario evitare la possibilità della seguente sequenza:

  • Il codice nel ciclo di attesa verifica la condizione, quando non è soddisfatto.

  • Il codice in un altro thread modifica la condizione in modo che sia soddisfatta.

  • Il codice in detto altro filo pulsa il blocco (che nessuno è ancora nell'attesa).

  • Il codice nei esegue attesa di loop un Wait fin dalla sua condizione non è stata soddisfatta.

Il metodo Wait richiede che il filo di attesa ha una serratura, dato che è l'unico modo in cui può essere sicuri che la condizione è in attesa sulla non cambierà tra il momento è testato e il tempo il codice esegue la Wait. Il metodo Pulse richiede un blocco perché è l'unico modo in cui può essere sicuri che se un altro thread è "impegnata" stesso per eseguire una Wait, il Pulse non avverrà fino a dopo l'altro filo effettivamente fa. Si noti che usando Wait all'interno di un blocco non garantisce che esso viene utilizzato in modo corretto, ma non c'è modo che l'utilizzo di Wait di fuori di un blocco potrebbe essere corretta.

Il design Wait / Pulse effettivamente funziona ragionevolmente bene se entrambe le parti collaborano. I maggiori punti deboli del progetto, secondo me, sono (1) non c'è alcun meccanismo per un filo di aspettare fino a qualsiasi di un certo numero di oggetti è pulsata; (2), anche se uno è "spegnimento" di un oggetto in modo tale che tutti i futuri cicli di attesa dovrebbero uscire immediatamente (probabilmente controllando una bandiera uscita), l'unico modo per garantire che qualsiasi Wait a cui un thread è impegnata per sé otterrà un Pulse è quello di acquisire il blocco, possibilmente in attesa indefinitamente per farlo diventare disponibile.

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