принцип работы Monitor.Pulse/Wait

223
15 февраля 2018, 12:56

Пишу программу. Есть Queue _readedBlocks. С ней работают три функции:

  1. Конструктор. При создании класса инициализируется очередь.
  2. EnqueueReaded
  3. DequeueReaded

    public static void EnqueueReaded(Block block)
    {
        lock (_readedBlocks)
        {
            _readedBlocks.Enqueue(block);
            Monitor.Pulse(_readedBlocks);
        }
    }
    public static Block DequeueReaded()
    {
        lock (_readedBlocks)
        {
            if (!_readedBlocks.Any())
            {
                if (endOfRead)
                    return null;
                Monitor.Wait(_readedBlocks);
            }
            return _readedBlocks.Dequeue();
        }
    }

Больше нигде в коде _readedBlocks никак не используется. У меня один поток заполняет _readedBlocks и два потока его опустошают. В какой то момент у меня вылетает исключение "Очередь пуста". Что я делаю не так? По сути же если очередь пуста, поток становится в ожидание. Как только в очередь добавляется элемент, первый из ждущих получает пульс и работает с его добычей, в то время, как второй должен сидеть и ждать, пока заполняющий поток не подтянет еще элементов. Что не тка в этой логике?

Answer 1

Кейс:

  1. Поток чтения 1 ушел в Wait
  2. Поток записи посылает Pulse и освобождает блокировку
  3. Поток чтения 2 входит внутрь секции и читает (MSDN пишет, что Pulse не гарантирует получения доступа к секции первым именно потока получившего Pulse)
  4. Поток чтения 2 покидает секцию
  5. Поток чтения 1 получает доступ, но очередь уже пуста

Для наглядности я модифицировал твой пример:

lock (_readedBlocks)
{
    Console.WriteLine($"Enter: {Thread.CurrentThread.ManagedThreadId}");
    if (!_readedBlocks.Any())
    {
        if (endOfRead)
            return null;
        Console.WriteLine($"Wait: {Thread.CurrentThread.ManagedThreadId}");
        Monitor.Wait(_readedBlocks);
        Console.WriteLine($"Release: {Thread.CurrentThread.ManagedThreadId}");
    }
    Console.WriteLine($"Dequeue: {Thread.CurrentThread.ManagedThreadId}"); 
    return _readedBlocks.Dequeue();
}

Перед exception получился вот такой output:

Wait: 4
Pulse: 3
Enter: 5
Dequeue: 5
Release: 4
Dequeue: 4

Имхо, в твоем случае, самой лайтовой правкой будет обернуть Wait как-нибудь вот так:

 do {
     Monitor.Wait(_readedBlocks);
 } while (!_readedBlocks.Any());
READ ALSO
Telegram Bot C# - команды в Telegram.Bot

Telegram Bot C# - команды в Telegram.Bot

Написал простого бота для Telegram на C# (консольное приложение), используя библиотеку TelegramBot

292
Обработка 502(BadGateway) в TelegramBot C#

Обработка 502(BadGateway) в TelegramBot C#

Подскажите, как добавить/исправить существующий обработчик bad gateway, сейчас код выглядит вот так:

183
Unity(C#) - Как перемещать объект?

Unity(C#) - Как перемещать объект?

Нужно сделать бесконечный цикл перемещения объекта: От стартовой позиции на определенное расстояние вправо, пройдя это расстояние, повернутся,...

192
Нужна практика c# [требует правки]

Нужна практика c# [требует правки]

Где можно взять практические задания по c# , чтобы можно было использовать в реальной работе

306