Синхронизация ThreadPool QueueUserWorkItem.NET

Я нанимаю ThreadPool. QueueUserWorkItem для проигрывания некоторых звуковых файлов и не зависающий GUI при выполнении так.

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

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

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

Править: private object sync = new Object(); lock (sync) { .......do sound here }

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

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

Править: выше результата Конвоя Блокировки @Aaronaught упомянут?

9
задан iTEgg 28 March 2010 в 20:31
поделиться

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

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


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

8
ответ дан 4 December 2019 в 11:04
поделиться

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

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

Примерно так:

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 в C # и перейдите в раздел «Очередь производителя / потребителя».

2
ответ дан 4 December 2019 в 11:04
поделиться

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

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

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

6
ответ дан 4 December 2019 в 11:04
поделиться

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

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

Хотя он очень прост, но потенциально может привести к проблемам:

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

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

2
ответ дан 4 December 2019 в 11:04
поделиться

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

private AutoResetEvent playEvent = new AutoResetEvent(true);

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

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

.
1
ответ дан 4 December 2019 в 11:04
поделиться

В соответствии с вашей правкой создайте поток следующим образом:

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
ответ дан 4 December 2019 в 11:04
поделиться

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

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

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

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

0
ответ дан 4 December 2019 в 11:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: