C # блокировки и многопоточность в очереди - PullRequest
2 голосов
/ 23 февраля 2012

Я пишу многопоточное приложение, и у меня возникают проблемы с доступом к очереди 2 потоков

Поток 1 помещает элементы в очередь для обработки Поток 2 удаляет элементы из очереди для обработки

Поток 1 запускается раз в минуту из-за характера данных, которые он извлекает.Поток 2 всегда работает, он удаляет элемент из очереди и спит в течение 100 мс.Я должен сделать это, чтобы убедиться, что я не перегружаю службу, которую она вызывает при удалении элемента из очереди.

Я предполагаю, что оба потока должны установить блокировку в очереди при добавлении или удалении элементов из нее.Есть ли еще какие-то соображения?Например, поток 1 имеет блокировку, а поток 2 пытается получить к ней доступ.Неужели поток 2 просто знает, что ждать и возобновить после снятия блокировки?

Было бы предпочтительнее использовать ConcurrentQueue и просто TryDequeue, а в случае сбоя просто перейдите в режим ожидания 100 мс?Заранее спасибо

Ответы [ 4 ]

8 голосов
/ 23 февраля 2012

Еще проще, если вы используете BlockingCollection<T>, как я использую в диспетчере консоли NuGet VS для моей реализации PostKey / WaitKey. Потребляющий поток вызывает Take(...), который будет блокироваться, пока другой поток не вызовет Add(...). Нет необходимости в опросе. Кроме того, вы можете передать токен отмены методу Take, чтобы другой поток мог остановить поток потребителя, если в данный момент он ожидает Add, который никогда не будет получен. Вот соответствующие методы:

private readonly BlockingCollection<VsKeyInfo> _keyBuffer = 
      new BlockingCollection<VsKeyInfo>();
private CancellationTokenSource _cancelWaitKeySource;

// place a key into buffer
public void PostKey(VsKeyInfo key)
{
    if (key == null)
    {
        throw new ArgumentNullException("key");
    }
    _keyBuffer.Add(key);
}

// signal thread waiting on a key to exit Take
public void CancelWaitKey()
{
    if (_isExecutingReadKey && !_cancelWaitKeySource.IsCancellationRequested)
    {
        _cancelWaitKeySource.Cancel();
    }
}

// wait for a key to be placed on buffer
public VsKeyInfo WaitKey()
{
    try
    {
        // raise the StartWaitingKey event on main thread
        RaiseEventSafe(StartWaitingKey);

        // set/reset the cancellation token
        _cancelWaitKeySource = new CancellationTokenSource();
        _isExecutingReadKey = true;

        // blocking call
        VsKeyInfo key = _keyBuffer.Take(_cancelWaitKeySource.Token);

        return key;
    }
    catch (OperationCanceledException)
    {
        return null;
    }
    finally
    {
        _isExecutingReadKey = false;
    }
}

Подробнее см. http://nuget.codeplex.com/SourceControl/changeset/view/45e353aca7f4#src%2fVsConsole%2fConsole%2fConsole%2fConsoleDispatcher.cs.

2 голосов
/ 23 февраля 2012

Пока вы блокируете один и тот же объект "синхронизация", поток 2 будет ожидать поток 1 и наоборот. Я думаю, что ConcurrentQueue - хорошая идея, потому что он уже указан как потокобезопасный.

1 голос
/ 23 февраля 2012

Если вы все равно будете спать по 100 мс, вы можете в значительной степени реализовать очередь любым удобным для вас способом, если она поточно-ориентированная. Простая очередь с блокировкой / мьютексом, ConcurrentQueue, все в порядке.

'Например, поток 1 имеет блокировку, а поток 2 пытается получить к ней доступ. Неужели поток 2 просто знает, что ждать и возобновить после снятия блокировки? - поток 2 ждет, пока блокировка не будет снята, да.

1 голос
/ 23 февраля 2012

ConcurrentQueue является поточно-ориентированным, поэтому вам не нужно блокировать, просто наберите TryDequeue, и у вас все будет в порядке, если нет другого специального кодирования.Не повторяйте его, хотя, согласно документации, он будет снимать на итерации (если это не то, что вы хотели): http://msdn.microsoft.com/en-us/library/dd287144.aspx

Перечисление представляет моментальный снимок содержимогоочередь.Он не отражает никаких обновлений в коллекции после вызова GetEnumerator .Перечислитель безопасно использовать одновременно с чтением и записью в очередь.

Честно говоря, я думаю, что было бы лучше, если бы поток 1 порождал поток (или Task) всякий раз, когда ему что-то нужносделано и используется какая-то дроссельная синхронизация (например, AutoResetEvent)

...