2010-03-28 18 views
8

Estoy utilizando ThreadPool.QueueUserWorkItem para reproducir algunos archivos de sonido y no colgar la GUI mientras lo hago..NET ThreadPool QueueUserWorkItem Sincronización

Está funcionando pero tiene un efecto secundario indeseable.

Mientras QueueUserWorkItem CallBack Proc se está ejecutando, no hay nada que impida que comience un nuevo hilo. Esto hace que las muestras en los hilos se superpongan.

¿Cómo puedo hacer para que espere a que el hilo que ya se está ejecutando termine de ejecutarse y solo luego ejecute la siguiente solicitud?

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

esto funciona. juega en los sonidos en orden.

pero algunas muestras se reproducen más de una vez cuando sigo enviando solicitudes de sonido antes de que finalice la reproducción. investigaré.

EDIT: ¿es el anterior resultado de Lock Convoy @Aaronaught mencionado?

+0

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

Respuesta

7

Este es un problema clásico de sincronización de subprocesos, donde tiene varios clientes que todos quieren usar el mismo recurso y necesitan controlar cómo lo acceden.En este caso particular, el sistema de sonido está dispuesto a reproducir más de un sonido al mismo tiempo (y esto a menudo es deseable), pero como no desea ese comportamiento, puede usar el bloqueo estándar para acceder al sistema de sonido. :

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

donde Sonido es cualquier objeto que esté utilizando para representar un sonido que se reproducirá. Este mecanismo es 'primero llegado, primero servido' cuando múltiples sonidos compiten por el tiempo de reproducción.


Como se menciona en otra respuesta, tenga cuidado de excesivo 'amontonamiento' de sonidos, como usted comenzará a atar ThreadPool.

+0

poniendo los sonidos que se deben reproducir en un solo AsyncProc y usando el Bloqueo (..) mi problema se ha resuelto. – iTEgg

6

Puede usar un solo hilo con una cola para reproducir todos los sonidos.

Cuando desee reproducir un sonido, inserte una solicitud en la cola e indique al hilo de reproducción que hay un nuevo archivo de sonido para reproducir. El hilo de reproducción de sonido ve la nueva solicitud y la reproduce. Una vez que el sonido se completa, verifica si hay más sonidos en la cola y, de ser así, reproduce el siguiente, de lo contrario, espera la próxima solicitud.

Un posible problema con este método es que si tiene demasiados sonidos que necesitan reproducirse, puede tener un retraso acumulado permanente de modo que los sonidos pueden tardar varios segundos o posiblemente incluso minutos tarde. Para evitar esto, es posible que desee poner un límite en el tamaño de la cola y soltar algunos sonidos si tiene demasiados.

+4

+1. En otras palabras, no use ThreadPool. Haz el enhebrado tú mismo. ThreadPool por diseño usa múltiples hilos para completar todos los elementos de trabajo en cola más en menos tiempo. –

+0

Ver también http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!1780.entry para una estrategia de F #, si eso es hasta su callejón. – Brian

+0

En realidad se puede utilizar un conjunto de subprocesos - tener una cola separada que, cuando un artículo llega y no hay un proceso de espera, colas de un elemento de trabajo. El elemento de trabajo tiene una devolución de llamada que manejará todos los elementos en espera. Cada cola solo hará cola si no hay nada en cola. Lo uso mucho en una aplicación comercial en tiempo real y funciona muy bien, y me impide administrar mis hilos manualmente. Pero vertido cada petición en el TreadPool es malo;) – TomTom

0

Según su editar, crear el hilo de esta manera:

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

y este será su punto de entrada a la rosca.

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

    /* play your sounds */ 
} 
+0

hilo t = new Thread (this.FireAttackProc, fireResult); da errores no se puede convertir de 'NietzscheBattleships.FireResult' a 'int' – iTEgg

2

El código más simple podría escribir sería la siguiente:

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

Acabase muy simple que pontentially podría producir problemas:

  1. Si juegas un montón de sonidos (más) simultáneamente habrá muchos bloqueos y muchos subprocesos de subprocesos se agotan.
  2. El orden en que colocaste los sonidos no es necesariamente el orden en que se reproducirán.

En la práctica, estos problemas solo deberían ser relevantes si reproduce muchos sonidos con frecuencia o si los sonidos son muy largos.

+0

el bloqueo suena prometedor. ¿hay alguna forma de emplearlo en lo siguiente? ThreadPool.QueueUserWorkItem (nuevo WaitCallback (FireResultProc), fireResult); – iTEgg

+0

Esto funciona, pero no asegura que los sonidos se reproducen en el orden correcto, lo que podría importante – dan

+1

No sólo no asegura que los sonidos se reproducen en el orden correcto, pero si muchos sonidos se reproducen en rápida sucesión Esto creará un enorme bloqueo contención: incluso podría agotar el grupo de subprocesos si los sonidos son lo suficientemente largos. – Aaronaught

0

El uso de ThreadPool no es el error. El error es poner en cola cada sonido como elemento de trabajo. Naturalmente, el grupo de subprocesos iniciará más subprocesos. Esto es lo que se supone que debe hacer.

Crea tu propia cola. Tengo uno (AsyncActionQueue). Pone en cola los artículos y cuando tiene un artículo, iniciará un artículo de trabajo ThreadPool, no uno por artículo, UNO (a menos que uno ya esté en cola y no terminado). La devolución de llamada básicamente independiza los elementos y los procesa.

Esto me permite tener colas X que comparten subprocesos Y (es decir, que no son subprocesos) y aún así obtener operaciones asincrónicas muy agradables. Lo uso para una aplicación de comercio de interfaz de usuario de comples: X ventanas (6, 8) se comunican con un clúster de servicio central (es decir, una serie de servicios) y todos usan colas asíncronas para mover elementos hacia adelante y hacia atrás (bueno, principalmente hacia la interfaz de usuario)

Una cosa que usted NECESITA tener en cuenta, y eso ya se ha dicho, es que si sobrecarga su cola, retrocederá. Qué hacer entonces depende de tu. Tengo un mensaje de ping/pong que se pone en cola regularmente para un servicio (desde la ventana) y si no se devuelve a tiempo, la ventana se pone gris y marca "Estoy agotado" hasta que se pone al día.

2

Una cola de productor/consumidor muy simple sería ideal aquí, ya que solo tiene 1 productor y 1 consumidor, puede hacerlo con un bloqueo mínimo.

No usar una sección crítica (lock declaración) en torno a la real Play método/operación ya que algunas personas están sugiriendo, puede fácilmente llegar con un lock convoy. Debe bloquear, pero solo debe hacerlo durante períodos de tiempo muy cortos, no mientras se reproduce un sonido, lo cual es una eternidad en el tiempo de la computadora.

Algo como esto:

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

Esto le permitirá a estrangular el número de sonidos que se está reproduciendo mediante el establecimiento de maxSize hasta cierto límite razonable, al igual que 5, después del cual simplemente se descartará nuevas solicitudes. La razón por la que uso un Thread en lugar de ThreadPool es simplemente para mantener una referencia al hilo administrado y poder proporcionar una limpieza adecuada.

Esto solo usa un hilo y un candado, por lo que nunca tendrá un convoy de candado, y nunca tendrá sonidos jugando al mismo tiempo.

Si tiene algún problema para entender esto o necesita más detalles, eche un vistazo a Threading in C# y diríjase a la sección "Cola de productor/cliente".

1

Otra opción, si se puede hacer que el (mayor) supuesto simplificador de que cualquier intento de jugar un segundo sonido, mientras que el primero sigue jugando sólo será ignorados, es el uso de un solo 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(); 
     } 
    }); 
} 

de este muerto fácil, con el inconveniente obvio que simplemente descartar "extra" suena en lugar de hacer cola ellos. Pero en este caso, puede ser exactamente lo que quiere, y podemos utilizar el grupo de subprocesos porque esta función regresará instantáneamente si ya se está reproduciendo un sonido. Básicamente está "libre de cerrojo".

0

nuevo flujo de datos Biblioteca TPL de Microsoft podría ser una buena solución para este tipo de cosas. Mira el video aquí: el primer ejemplo de código demostrado se ajusta exactamente a tus necesidades.

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