Почему Monitor.Pulse() не дает эффекта?
От: rg45 СССР  
Дата: 30.11.09 19:05
Оценка:
Привет всем!

Суть проблемы в двух словах: есть два потока, первый поток ожидает сигнала посредством Monitor.Wait(), второй поток сигнализирует посредством Monitor.Pulse() или Monitor.PulseAll(), но первый поток после этого не выходит из состояния ожидания. Вот минимизированный пример, воспроизводящий проблему:
using System;
using System.Threading;

namespace WaitPulseTest
{
    class Program
    {
        static object _Lock = new object();
        static bool _NeedExit = false;

        static void Main(string[] args)
        {
            lock (_Lock)
            {
                Thread thread = new Thread(Run);
                thread.Start();
                Console.WriteLine("Waiting for thread started . . .");
                Monitor.Wait(_Lock);
            }
            Console.WriteLine("Press any key to exit . . ."); //Сюда мы не приходим
            Console.ReadKey(true);
            lock (_Lock)
            {
                _NeedExit = true;
            }
        }

        static void Run()
        {
            lock (_Lock)
            {
                Console.WriteLine("Thread is started.");
                do
                {
                    Monitor.PulseAll(_Lock);
                    Thread.Sleep(100);
                } while (!_NeedExit);
                Console.WriteLine("Thread is finished.");
            }
        }
    }
}


Кто виноват и что делать?
--
Справедливость выше закона. А человечность выше справедливости.
Re: Почему Monitor.Pulse() не дает эффекта?
От: Пельмешко Россия blog
Дата: 30.11.09 19:21
Оценка:
Здравствуйте, rg45, Вы писали:

R>Привет всем!


R>Суть проблемы в двух словах: есть два потока, первый поток ожидает сигнала посредством Monitor.Wait(), второй поток сигнализирует посредством Monitor.Pulse() или Monitor.PulseAll(), но первый поток после этого не выходит из состояния ожидания. Вот минимизированный пример, воспроизводящий проблему:


R>Кто виноват и что делать?


У Вас каша получилась какая-то, зачем внутри lock'а монитора дергать Monitor.Wait(), .Pulse()? Второй поток не сможет никогда зайти в lock, так как первый поток висит внутри lock'а по Monitor.Wait().

Если нужно между потоками сигнализировать и дожидаться событий, то может стоит попробовать использовать ManualResetEvent?
Если уж надо манипулировать именно Monitor'ами на уровне Wait/Pulse, то зачем помещать код в lock?
Re[2]: Почему Monitor.Pulse() не дает эффекта?
От: rg45 СССР  
Дата: 30.11.09 19:23
Оценка: +1
Здравствуйте, Пельмешко, Вы писали:


П>У Вас каша получилась какая-то, зачем внутри lock'а монитора дергать Monitor.Wait(), .Pulse()? Второй поток не сможет никогда зайти в lock, так как первый поток висит внутри lock'а по Monitor.Wait().


Monitor.Wait() и .Pulse() можно дергать только внутри lock — это без вариантов — так они задуманы и устроены.
--
Справедливость выше закона. А человечность выше справедливости.
Re[3]: Почему Monitor.Pulse() не дает эффекта?
От: Пельмешко Россия blog
Дата: 30.11.09 19:26
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, Пельмешко, Вы писали:



П>>У Вас каша получилась какая-то, зачем внутри lock'а монитора дергать Monitor.Wait(), .Pulse()? Второй поток не сможет никогда зайти в lock, так как первый поток висит внутри lock'а по Monitor.Wait().


R>Monitor.Wait() и .Pulse() можно дергать только внутри lock — это без вариантов — так они задуманы и устроены.


Да, прошу прощения, совсем забыл...
Re: Почему Monitor.Pulse() не дает эффекта?
От: Finder_b  
Дата: 30.11.09 19:32
Оценка: 31 (2)
        static void Run()
        {
            lock (_Lock)
            {
                do
                {
                    Monitor.PulseAll(_Lock);
                    Thread.Sleep(100);
                } while (!_NeedExit);
                Console.WriteLine("Thread is finished.");
            }
        }

По тому что цикл do {} while (!_NeedExit); не завершится пока не будет установлен флаг _NeedExit и как следствие не освободит _Lock, а функция Monitor.Wait не вернет управление пока не будет освобожден _Lock. В результате имеем деадлок.

R>что делать?

Учить многопоточность, в частности паттерн монитор, рабочий код:

        static void Main(string[] args)
        {
            lock (_Lock)
            {
                Thread thread = new Thread(Run);
                thread.Start();
                Console.WriteLine("Waiting for thread started . . .");
                Monitor.Wait(_Lock);
            }
            Console.WriteLine("Press any key to exit . . ."); //Сюда мы не приходим
            Console.ReadKey(true);
            lock (_Lock)
            {
                _NeedExit = true;
                Monitor.PulseAll(_Lock);
            }
        }

        static void Run()
        {
            lock (_Lock)
            {
                Console.WriteLine("Thread is started.");
                Monitor.PulseAll(_Lock);
                do
                {
                    Monitor.Wait(_Lock, 100);
                    // делаем еще чтото
                } while (!_NeedExit);
                Console.WriteLine("Thread is finished.");
            }
        }
Re: Почему Monitor.Pulse() не дает эффекта?
От: desco США http://v2matveev.blogspot.com
Дата: 30.11.09 19:33
Оценка: 10 (1)
Здравствуйте, rg45, Вы писали:

<skipped/>
R>Кто виноват и что делать?

все просто, первый поток выйдет из Wait, как только второй поток покинет границы lock(_Lock). А этого не происходит из-за взаимной блокировки.
Re[2]: Почему Monitor.Pulse() не дает эффекта?
От: rg45 СССР  
Дата: 30.11.09 19:59
Оценка:
F_>[/code]
F_>По тому что цикл do {} while (!_NeedExit); не завершится пока не будет установлен флаг _NeedExit и как следствие не освободит _Lock, а функция Monitor.Wait не вернет управление пока не будет освобожден _Lock. В результате имеем деадлок.

R>>что делать?

F_>Учить многопоточность, в частности паттерн монитор, рабочий код:
F_>...

Для полноты картины, все же, вкину свои пять копеек. Специфика моей задачи такова, что мне Monitor.Pulse() нужен в каждой итерации цикла, просто на минимизированном примере этот факт не очевиден. Но вызов этой функции можно спокойно внести обратно внутрь цикла, следующий код тоже рабочий:
static void Run()
{
  lock (_Lock)
  {
    Console.WriteLine("Thread is started.");
    do
    {
      Monitor.PulseAll(_Lock);
      Monitor.Wait(_Lock, 100);
      // делаем еще что-то
    } while (!_NeedExit);
    Console.WriteLine("Thread is finished.");
  }
}


Ошибка моя была не в том, что я не по месту вызывал Pulse(), а в том, что неправильно организовал ожидание в цикле второго потока: вместо Monitor.Wait() использовал Thread.Sleep().

Большое человеческое спасибо
--
Справедливость выше закона. А человечность выше справедливости.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.