Суть проблемы в двух словах: есть два потока, первый поток ожидает сигнала посредством 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.");
}
}
}
}
Кто виноват и что делать?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Привет всем!
R>Суть проблемы в двух словах: есть два потока, первый поток ожидает сигнала посредством Monitor.Wait(), второй поток сигнализирует посредством Monitor.Pulse() или Monitor.PulseAll(), но первый поток после этого не выходит из состояния ожидания. Вот минимизированный пример, воспроизводящий проблему:
R>Кто виноват и что делать?
У Вас каша получилась какая-то, зачем внутри lock'а монитора дергать Monitor.Wait(), .Pulse()? Второй поток не сможет никогда зайти в lock, так как первый поток висит внутри lock'а по Monitor.Wait().
Если нужно между потоками сигнализировать и дожидаться событий, то может стоит попробовать использовать ManualResetEvent?
Если уж надо манипулировать именно Monitor'ами на уровне Wait/Pulse, то зачем помещать код в lock?
П>У Вас каша получилась какая-то, зачем внутри lock'а монитора дергать Monitor.Wait(), .Pulse()? Второй поток не сможет никогда зайти в lock, так как первый поток висит внутри lock'а по Monitor.Wait().
Monitor.Wait() и .Pulse() можно дергать только внутри lock — это без вариантов — так они задуманы и устроены.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, Пельмешко, Вы писали:
П>>У Вас каша получилась какая-то, зачем внутри lock'а монитора дергать Monitor.Wait(), .Pulse()? Второй поток не сможет никогда зайти в lock, так как первый поток висит внутри lock'а по Monitor.Wait().
R>Monitor.Wait() и .Pulse() можно дергать только внутри lock — это без вариантов — так они задуманы и устроены.
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.");
}
}
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().
Большое человеческое спасибо
--
Справедливость выше закона. А человечность выше справедливости.