2014-11-24 4 views
5

Possiedo un servizio che garantisce che venga visualizzato esattamente un solo popup allo stesso tempo. AddPopupAsync può essere chiamato contemporaneamente, vale a dire un popup è aperta, mentre un altro 10 AddPopupAsync richieste cadono nel codice:.Come rendere i metodi asincroni che utilizzano un thread safe code

public async Task<int> AddPopupAsync(Message message) 
{ 
    //[...] 

    while (popupQueue.Count != 0) 
    { 
     await popupQueue.Peek(); 
    } 

    return await Popup(interaction); 
} 

ma posso macchiare due cose indesiderate che possono accadere a causa della mancanza di sicurezza dei thread:

  1. Se la coda è vuota, Peek sarà un'eccezione
  2. Se un thread a è preempted prima della prima istruzione in popup, un altro thread B non attendere la comparsa in attesa un dato che la coda è ancora vuota.

metodo Popup funziona con un TaskCompletionSource e prima di chiamare il suo metodo SetResult, popupQueue.Dequeue() si chiama.

Penso utilizzando l'atomica TryPeek da ConcurrentQueue al fine di rendere # 1 thread-safe:

do 
{ 
    Task<int> result; 

    bool success = popupQueue.TryPeek(out result); 

    if (!success) break; 
    await result; 
} 
while (true); 

Poi ho letto di un AsyncProducerConsumerCollection ma non sono sicuro se questa è la soluzione più semplice.

Come si può garantire la sicurezza del filo in modo semplice? Grazie.

+1

Penso che tu abbia risposto alla tua stessa domanda. 'ConcurrentQueue ' ha ciò che è necessario integrato. –

+0

La tua semantica corrente è che tutte le chiamate a 'AddPopupAsync' non verranno completate fino a quando non verranno visualizzati tutti i popup (non solo quello mostrato da quella particolare chiamata). Sei sicuro che sia la semantica che vuoi? –

+0

@Stephen Cleary Poichè PopupView e PopupViewModel sono singleton, lo stato della finestra popup modale viene modificato ogni volta che viene richiamato Popup. Ed è per questo che sto usando una coda. – user4287276

risposta

8

Per aggiungere semplicemente thread-safety è necessario utilizzare ConcurrentQueue. È un'implementazione thread-safe di una coda e puoi utilizzarla nello stesso modo in cui utilizzi una coda senza preoccuparti della concorrenza.

Tuttavia, se si desidera una coda è possibile await in modo asincrono (il che significa che non sei occupato-attesa o il blocco di un filo in attesa) si dovrebbe usare di BufferBlock TPL Dataflow che è molto simile a AsyncProducerConsumerCollection solo già implementato per voi da MS:

var buffer = new BufferBlock<int>(); 
await buffer.SendAsync(3); // Instead of enqueue. 
int item = await buffer.ReceiveAsync(); // instead of dequeue. 

ConcurrentQueue va bene per filo sicurezza, ma dispendioso in alti livelli di contesa. BufferBlock non è solo thread-safe ma offre anche un coordinamento asincrono (di solito tra un consumatore e un produttore).

+0

Ma ConcurrentQueue non indica che il codice che utilizza ConcurrentQueue è thread-safe, lo fa (vedere # 2 nella mia domanda)? – user4287276

+0

@ user4287276 'ConcurrentQueue' assicura che ogni chiamata a uno dei suoi metodi sia sicura da thread. Non è un meccanismo di blocco. Se hai bisogno di sincronizzazione nel tuo codice dovresti usare un 'lock' (o' AsyncLock' se appropriato). – i3arnon