2010-03-28 4 views
8

Я использую ThreadPool.QueueUserWorkItem для воспроизведения некоторых звуковых файлов, а при этом не подвешивать графический интерфейс..NET ThreadPool QueueUserWorkItem Синхронизация

Он работает, но имеет нежелательный побочный эффект.

Пока выполняется QueueUserWorkItem CallBack Proc, нет ничего, чтобы остановить его от запуска нового потока. Это заставляет образцы в потоках перекрываться.

Как я могу сделать это так, чтобы он ожидал завершения уже запущенного потока и только затем выполнил следующий запрос?

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

это работает. играет в звуках по порядку.

, но некоторые образцы воспроизводятся более одного раза, когда я продолжаю отправлять звуковые запросы до того, как один из них будет завершен. будет исследовать.

РЕДАКТИРОВАТЬ: Вышеупомянутый результат Lock Convoy @Aaronaught упоминается?

+0

Возможный дубликат ?: http://stackoverflow.com/questions/1902384/make-a-backgroundworker-do-several-operations-sequentially-without-freezing-the-f/1907501#1907501 – Oliver

ответ

7

Это классическая проблема синхронизации потоков, где у вас несколько клиентов, которые все хотят использовать один и тот же ресурс и должны контролировать, как они к нему обращаются.В этом конкретном случае звуковая система готова одновременно воспроизводить несколько звуков (и это часто бывает желательно), но поскольку вы не хотите этого поведения, вы можете использовать стандартную блокировку для доступа к шлюзу для звуковой системы :

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

где Звук - это любой объект, который вы используете для представления воспроизводимого звука. Этот механизм «первым пришел, первым обслужен», когда несколько звуков соперничают за время воспроизведения.


Как уже упоминалось в другой ответ, будьте осторожны чрезмерной «нагромождения» звуков, как вы начнете перевязать ThreadPool.

+0

, поместив звуки, которые необходимо воспроизвести в одном AsyncProc, и используя Lock (..), моя проблема решена. – iTEgg

6

Вы можете использовать один поток с очередью для воспроизведения всех звуков.

Если вы хотите воспроизвести звук, вставьте запрос в очередь и сообщите игровому потоку, что есть новый звуковой файл для воспроизведения. Поток звукового воспроизведения видит новый запрос и воспроизводит его. Как только звук завершится, он проверяет, есть ли в очереди больше звуков, и если так играет следующее, в противном случае он ждет следующего запроса.

Одна из возможных проблем с этим методом заключается в том, что если у вас слишком много звуков, которые необходимо воспроизвести, вы можете получить постоянно растущее отставание, чтобы звуки могли наступать несколько секунд или, возможно, даже минуты опоздать. Чтобы этого избежать, вы можете ограничить размер очереди и отбросить некоторые звуки, если у вас слишком много.

+4

+1. Другими словами, не используйте ThreadPool. Нарисуйте сами. ThreadPool по дизайну использует несколько потоков для выполнения всех поставленных рабочих элементов за меньшее время. –

+0

См. Также http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!1780.entry для стратегии F #, если это ваш переулок. – Brian

+0

На самом деле вы можете использовать threadpool - иметь отдельную очередь, которая, когда элемент входит и нет процесса ожидания, QUEUES A WORK ITEM. Рабочий элемент имеет обратный вызов, который будет обрабатывать все элементы ожидания. Каждая очередь будет стоять только в очереди, если ничего не поставлено в очередь. Я использую это в реальном времени для торгового приложения, и он работает хорошо - и не позволяет мне управлять моими потоками вручную. Но сброс каждого запроса в TreadPool плох;) – TomTom

0

В соответствии с Вашим редактировать, создать свою нить, как это:

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

И это будет ваша точка входа резьбы.

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

    /* play your sounds */ 
} 
+0

Тема t = новая тема (this.FireAttackProc, fireResult); дает ошибки. не может конвертировать из «NietzscheBattleships.FireResult» в «int» – iTEgg

2

Самый простой код, который вы могли бы написать будет выглядеть следующим образом:

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

Allthough очень просто это pontentially может принести проблемы:

  1. Если вы играете много (более) звуков одновременно будет много замков, и много потоков threadpool истощаются.
  2. Порядок, в котором вы выставили звуки, необязательно, чтобы они были воспроизведены.

на практике эти проблемы должны быть релевантными, если вы часто играете много звуков или если звуки очень длинные.

+0

блокировки звуков многообещающие. есть ли способ использования этого в следующем? ThreadPool.QueueUserWorkItem (новый WaitCallback (FireResultProc), fireResult); – iTEgg

+0

Это работает, но не гарантирует, что звуки будут воспроизводиться в правильном порядке, что может быть важно – dan

+1

Это не только не гарантирует, что звуки будут воспроизводиться в правильном порядке, но если многие звуки будут воспроизводиться быстро, это создаст огромную блокировку Конфликт - он может даже исчерпать пул потоков, если звуки достаточно длинные. – Aaronaught

0

Использование ThreadPool не является ошибкой. Ошибка представляет собой очередность каждого звука в качестве рабочего элемента. Естественно, пул потоков запускает больше потоков. Это то, что он должен делать.

Создайте собственную очередь. У меня есть один (AsyncActionQueue). Он ставит в очередь элементы, и когда у него есть элемент, он запустит WorkPlayer ThreadPool - не один за элемент, ОДИН (если он уже не поставлен в очередь и не завершен). Обратный вызов в основном не отвечает требованиям и обрабатывает их.

Это позволяет мне иметь очереди X совместно использовать нити Y (т. Е. Не течь) и все равно получать очень приятные асинхронные операции. Я использую это для торгового приложения для компиляции UI - X-окна (6, 8), связывающиеся с центральным кластером служб (то есть с несколькими службами), и все они используют асинхронные очереди для перемещения элементов вперед и назад (ну, в основном, в сторону пользовательского интерфейса).

Одна вещь, о которой вам НУЖНО знать - и это уже сказано, - это то, что если вы перегрузите свою очередь, она отпадет. Что делать тогда зависит от вашего. У меня есть сообщение ping/pong, которое регулярно ставится в очередь на службу (из окна), а если не возвращается вовремя, окно становится серым, отмечая «Я устарело», пока оно не настигнет.

2

Очень простая очередь производителей/потребителей будет идеальной здесь - поскольку у вас есть только 1 производитель и 1 потребитель, вы можете сделать это с минимальной блокировкой.

Не использовать критическую секцию (lock заявление) вокруг фактического Play метода/операции, как некоторые люди предполагают, вы можете очень легко в конечном итоге с lock convoy. Вам нужно блокировать, но вы должны делать это только в течение очень коротких промежутков времени, а не во время воспроизведения звука, что является вечностью в компьютерном времени.

Что-то вроде этого:

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

Это позволит вам душить количество звуков воспроизводимой установки maxSize в какой-то разумный предел, как 5, после чего он будет просто выбросить новые запросы. Причина, по которой я использую Thread вместо ThreadPool, - это просто поддерживать ссылку на управляемый поток и быть в состоянии обеспечить надлежащую очистку.

Используется только один поток и один замок, поэтому у вас никогда не будет конвойной блокировки и никогда не будет воспроизводиться звук одновременно.

Если у вас возникли проблемы с пониманием этого или требуется более подробная информация, посмотрите на Threading in C# и перейдите в раздел «Продюсер/очередь потребителей».

1

Другой вариант, если вы можете сделать (основной) упрощающее предположение, что любые попытки играть второй звук в то время как первая по-прежнему играть будет просто игнорировали, чтобы использовать одно событие:

private AutoResetEvent playEvent = new AutoResetEvent(true); 

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

Это мертвое легко, с очевидным недостатком, что он просто отбросит «лишние» звуки вместо того, чтобы их ставить в очередь. Но в этом случае это может быть именно то, что вы хотите, и мы используем пул потоков, потому что эта функция немедленно вернется, если звук уже воспроизводится. Это в основном «без блокировки».

0

Новая библиотека Microsoft TPL Dataflow Library может быть хорошим решением для такого рода вещей. Посмотрите видео здесь - первый пример кода продемонстрировал свои требования в точности точно.

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

Смежные вопросы