2010-03-28 18 views
8

Sto utilizzando ThreadPool.QueueUserWorkItem per riprodurre alcuni file audio e non riagganciando la GUI mentre lo faccio..NET ThreadPool QueueUserWorkItem Sincronizzazione

Sta funzionando ma ha un effetto collaterale indesiderabile.

Mentre viene eseguito il processo di richiamo di QueueUserWorkItem CallBack, non c'è nulla che impedisca l'avvio di un nuovo thread. Ciò causa la sovrapposizione dei campioni nei thread.

Come posso fare in modo che sia in attesa del completamento del thread già in esecuzione e solo dopo eseguire la richiesta successiva?

EDIT: private object sync = new Object(); lock (sync) { .......do sound here }

questo funziona. gioca nei suoni in ordine.

ma alcuni campioni vengono riprodotti più di una volta quando continuo a inviare richieste sonore prima della fine della riproduzione. indagherà.

MODIFICA: è il precedente risultato di Lock Convoy @Aaronaught citato?

+0

Eventuali duplicati ?: http://stackoverflow.com/questions/1902384/make-a-backgroundworker-do-several-operations-sequentially-without-freezing-the-f/1907501#1907501 – Oliver

risposta

7

Questo è un classico problema di sincronizzazione dei thread, in cui si hanno più client che tutti desiderano utilizzare la stessa risorsa e devono controllare come accedervi.In questo caso particolare, il sistema audio è disposto a suonare più di un suono allo stesso tempo (e spesso è desiderabile), ma dal momento che non si desidera questo comportamento, è possibile utilizzare il blocco standard per accedere al sistema audio. :

public static class SequentialSoundPlayer 
{ 
    private static Object _soundLock = new object(); 

    public static void PlaySound(Sound sound) 
    { 
     ThreadPool.QueueUserWorkItem(AsyncPlaySound, sound); 
    } 

    private static void AsyncPlaySound(Object state) 
    { 
     lock (_soundLock) 
     { 
      Sound sound = (Sound) state; 
      //Execute your sound playing here... 
     } 
    } 
} 

dove Suono è l'oggetto che si sta utilizzando per rappresentare un suono da riprodurre. Questo meccanismo è "primo arrivato, primo servito" quando più suoni si contendono il tempo di gioco.


Come detto in un'altra risposta, fare attenzione eccessiva 'pile-up' di suoni, come potrai iniziare a legare il ThreadPool.

+0

inserendo i suoni che devono essere riprodotti in un singolo AsyncProc e utilizzando il blocco (..) il mio problema è stato risolto. – iTEgg

6

È possibile utilizzare una singola discussione con una coda per riprodurre tutti i suoni.

Quando si desidera riprodurre un suono, inserire una richiesta nella coda e segnalare al thread di riproduzione che è in esecuzione un nuovo file audio. Il thread di riproduzione audio vede la nuova richiesta e la riproduce. Una volta completato il suono, controlla se ci sono altri suoni nella coda e in tal caso riproduce il successivo, altrimenti attende la richiesta successiva.

Un possibile problema con questo metodo è che se si dispone di troppi suoni che devono essere riprodotti è possibile ottenere un arretrato sempre crescente in modo che i suoni possano arrivare a diversi secondi o anche a qualche minuto di ritardo. Per evitare ciò, potresti voler limitare la dimensione della coda e rilasciare alcuni suoni se ne hai troppi.

+4

+1. In altre parole, non utilizzare ThreadPool. Fai tu stesso la filettatura. ThreadPool by design utilizza più thread per completare tutti gli elementi di lavoro in coda in minor tempo. –

+0

Vedere anche http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!1780.entry per una strategia F #, se questo è il tuo caso. – Brian

+0

In realtà, è possibile utilizzare un threadpool: disporre di una coda separata che, quando arriva un elemento e non vi è alcun processo in attesa, tratti un elemento di lavoro. L'oggetto di lavoro ha un callback che gestirà tutti gli elementi in attesa. Ogni coda si accoderà solo se non c'è nulla in coda. Lo uso molto in un'applicazione di trading in tempo reale e funziona bene - e mi impedisce di gestire i miei thread manualmente. Ma scaricare ogni richiesta nel TreadPool non è buona;) – TomTom

0

Come per la tua modifica, creare il filo in questo modo:

MySounds sounds = new MySounds(...); 
Thread th = new Thread(this.threadMethod, sounds); 
th.Start(); 

E questo sarà il vostro punto di ingresso thread.

private void threadMethod (object obj) 
{ 
    MySounds sounds = obj as MySounds; 
    if (sounds == null) { /* do something */ } 

    /* play your sounds */ 
} 
+0

Thread t = new Thread (this.FireAttackProc, fireResult); dà errori. non è possibile convertire da 'NietzscheBattleships.FireResult' a 'int' – iTEgg

2

Il codice più semplice si potrebbe scrivere sarebbe la seguente:

private object playSoundSync = new object(); 
public void PlaySound(Sound someSound) 
{ 
    ThreadPool.QueueUserWorkItem(new WaitCallback(delegate 
    { 
     lock (this.playSoundSync) 
     { 
     PlaySound(someSound); 
     } 
    })); 
} 

Allthough molto semplice pontentially potrebbe produrre problemi:

  1. Se si gioca un sacco di (più) i suoni contemporaneamente ci saranno un sacco di lock e molti thread threadpool si esauriranno.
  2. L'ordine in cui sono stati inseriti i suoni non è necessariamente l'ordine in cui verranno riprodotti.

in pratica questi problemi dovrebbero essere rilevanti solo se suoni spesso un sacco di suoni o se i suoni sono molto lunghi.

+0

suoni di blocco promettenti. c'è qualche modo di impiegarlo nel seguito? ThreadPool.QueueUserWorkItem (new WaitCallback (FireResultProc), fireResult); – iTEgg

+0

Funziona, ma non garantisce che i suoni vengano riprodotti nell'ordine corretto, il che potrebbe essere importante – dan

+1

Non solo non garantisce che i suoni vengano riprodotti nell'ordine corretto, ma se molti suoni vengono riprodotti in rapida successione ciò creerà un blocco enorme contesa - potrebbe anche esaurire il pool di thread se i suoni sono abbastanza lunghi. – Aaronaught

0

L'utilizzo di ThreadPool non è l'errore. L'errore è in coda ogni suono come elemento di lavoro. Naturalmente il pool di thread inizierà più thread. Questo è quello che dovrebbe fare.

Costruisci la tua coda. Ne ho uno (AsyncActionQueue). Accoda gli oggetti e quando ha un oggetto avvierà un oggetto ThreadPool - non uno per articolo, UNO (a meno che uno non sia già in coda e non finito). In pratica, la callback annulla gli oggetti e li elabora.

Questo mi consente di avere code X che condividono thread Y (cioè non sprecano thread) e che ottengono comunque operazioni asincrone molto belle. Lo uso per un'applicazione di compravendita dell'interfaccia utente di comples - X windows (6, 8) che comunica con un cluster di servizi centrale (ovvero un numero di servizi) e usano tutte le code asincrone per spostare gli elementi avanti e indietro (beh, soprattutto verso l'interfaccia utente).

Una cosa di cui si ha bisogno di essere a conoscenza - e che è già stato detto - è che se si sovraccarica la coda, si ripiegherà. Cosa fare quindi dipende dal tuo. Ho un messaggio ping/pong che viene accodato regolarmente ad un servizio (dalla finestra) e se non viene restituito in tempo, la finestra diventa grigia e segna "I am stale" fino a quando non raggiunge.

2

Una coda di produzione/consumatore molto semplice sarebbe l'ideale qui - dal momento che hai solo 1 produttore e 1 consumatore puoi farlo con un blocco minimo.

Non utilizzare una sezione critica (lock dichiarazione) intorno l'attuale metodo di Play/funzionamento come alcuni suggeriscono, si può facilmente finire con un lock convoy. Hai bisogno di bloccare, ma dovresti farlo solo per brevissimi periodi di tempo, non mentre un suono è effettivamente in riproduzione, che è un'eternità nel tempo del computer.

Qualcosa di simile a questo:

public class SoundPlayer : IDisposable 
{ 
    private int maxSize; 
    private Queue<Sound> sounds = new Queue<Sound>(maxSize); 
    private object sync = new Object(); 
    private Thread playThread; 
    private bool isTerminated; 

    public SoundPlayer(int maxSize) 
    { 
     if (maxSize < 1) 
      throw new ArgumentOutOfRangeException("maxSize", maxSize, 
       "Value must be > 1."); 
     this.maxSize = maxSize; 
     this.sounds = new Queue<Sound>(); 
     this.playThread = new Thread(new ThreadStart(ThreadPlay)); 
     this.playThread.Start(); 
    } 

    public void Dispose() 
    { 
     isTerminated = true; 
     lock (sync) 
     { 
      Monitor.PulseAll(sync); 
     } 
     playThread.Join(); 
    } 

    public void Play(Sound sound) 
    { 
     lock (sync) 
     { 
      if (sounds.Count == maxSize) 
      { 
       return; // Or throw exception, or block 
      } 
      sounds.Enqueue(sound); 
      Monitor.PulseAll(sync); 
     } 
    } 

    private void PlayInternal(Sound sound) 
    { 
     // Actually play the sound here 
    } 

    private void ThreadPlay() 
    { 
     while (true) 
     { 
      lock (sync) 
      { 
       while (!isTerminated && (sounds.Count == 0)) 
        Monitor.Wait(sync); 
       if (isTerminated) 
       { 
        return; 
       } 
       Sound sound = sounds.Dequeue(); 
       Play(sound); 
      } 
     } 
    } 
} 

Questo vi permetterà di andare a diminuire il numero di suoni in riproduzione impostando maxSize ad un limite ragionevole, come 5, dopo di che punto si sarà semplicemente gettare nuove richieste. Il motivo per cui utilizzo uno Thread anziché ThreadPool è semplicemente quello di mantenere un riferimento al thread gestito ed essere in grado di fornire una corretta pulizia.

Questo utilizza solo un thread e un blocco, quindi non si avrà mai un convoglio di blocco e non verranno mai riprodotti suoni allo stesso tempo.

Se hai problemi a capire questo, o hai bisogno di maggiori dettagli, dai un'occhiata a Threading in C# e vai alla sezione "Producer/Consumer Queue".

1

Un'altra opzione, se si può fare il (maggiore) ipotesi semplificativa che qualsiasi tentativo di svolgere un secondo suono mentre il primo è ancora in riproduzione sarà solo ignorato, è quello di utilizzare un singolo evento:

private AutoResetEvent playEvent = new AutoResetEvent(true); 

public void Play(Sound sound) 
{ 
    ThreadPool.QueueUserWorkItem(s => 
    { 
     if (playEvent.WaitOne(0)) 
     { 
      // Play the sound here 
      playEvent.Set(); 
     } 
    }); 
} 

Questo è facile, con l'ovvio svantaggio che semplicemente scarterà i suoni "extra" invece di metterli in coda. Ma in questo caso, potrebbe essere esattamente quello che vuoi, e useremo il pool di thread perché questa funzione ritornerà istantaneamente se un suono è già in riproduzione. È fondamentalmente "senza blocco".

0

La nuova libreria TPL Dataflow di Microsoft potrebbe essere una buona soluzione per questo genere di cose. Guarda qui il video: il primo esempio di codice dimostrato soddisfa esattamente le tue esigenze.

http://channel9.msdn.com/posts/TPL-Dataflow-Tour

Problemi correlati