C# Threading проблема с AutoResetEvent
Как правильно синхронизировать это? На данный момент возможно, что SetData
называется после e.WaitOne()
завершил так d
может быть уже установлено другое значение. Я попытался вставить блокировки, но это привело к тупику.
AutoResetEvent e = new AutoResetEvent(false);
public SetData(MyData d)
{
this.d=d;
e.Set(); // notify that new data is available
}
// This runs in separate thread and waits for d to be set to a new value
void Runner()
{
while (true)
{
e.WaitOne(); // waits for new data to process
DoLongOperationWith_d(d);
}
}
Будет ли лучшее решение ввести новую логическую переменную dataAlreadyBeenSetAndWaitingToBeProcessed
это установлено в SetData
к истине и в конце DoLongOperationWith_d
это может быть установлено в true, так что если SetData
вызывается с этой переменной, установленной в true, она может просто вернуться?
3 ответа
Это не проверено, но это элегантный способ сделать это с примитивами на основе.net:
class Processor<T> {
Action<T> action;
Queue<T> queue = new Queue<T>();
public Processor(Action<T> action) {
this.action = action;
new Thread(new ThreadStart(ThreadProc)).Start();
}
public void Queue(T data) {
lock (queue) {
queue.Enqueue(data);
Monitor.Pulse(queue);
}
}
void ThreadProc() {
Monitor.Enter(queue);
Queue<T> copy;
while (true) {
if (queue.Count == 0) {
Monitor.Wait(queue);
}
copy = new Queue<T>(queue);
queue.Clear();
Monitor.Exit(queue);
foreach (var item in copy) {
action(item);
}
Monitor.Enter(queue);
}
}
}
Пример программы:
class Program {
static void Main(string[] args) {
Processor<int> p = new Processor<int>((data) => { Console.WriteLine(data); });
p.Queue(1);
p.Queue(2);
Console.Read();
p.Queue(3);
}
}
Это не версия очереди, версия очереди может быть предпочтительнее:
object sync = new object();
AutoResetEvent e = new AutoResetEvent(false);
bool pending = false;
public SetData(MyData d)
{
lock(sync)
{
if (pending) throw(new CanNotSetDataException());
this.d=d;
pending = true;
}
e.Set(); // notify that new data is available
}
void Runner() // this runs in separate thread and waits for d to be set to a new value
{
while (true)
{
e.WaitOne(); // waits for new data to process
DoLongOperationWith_d(d);
lock(sync)
{
pending = false;
}
}
}
Здесь есть два, возможно, тревожных сценария.
1:
- DoLongOperationWith_d (d) заканчивается.
- SetData() вызывается, сохраняя новое значение в d.
- e.WaitOne () вызывается, но так как значение уже установлено, поток ждет вечно.
Если это твоя забота, я думаю, ты можешь расслабиться. Из документации видно, что
Если поток вызывает WaitOne, когда AutoResetEvent находится в сигнальном состоянии, поток не блокируется. AutoResetEvent немедленно освобождает поток и возвращается в состояние без сигнала.
Так что это не проблема. Однако, в зависимости от того, как и когда вызывается SetData(), вы можете иметь дело с более серьезными
2:
- Вызывается SetData(), сохраняя новое значение в d и пробуждая бегуна.
- DoLongOperationWith_d (d) запускается.
- SetData() вызывается снова, сохраняя новое значение в d.
- SetData() вызывается снова! Старое значение d потеряно навсегда; DoLongOperationWith_d() никогда не будет вызываться для него.
Если это ваша проблема, самый простой способ ее решения - параллельная очередь. Реализации имеются в большом количестве.
Вы можете использовать 2 события,
AutoResetEvent e = new AutoResetEvent(false);
AutoResetEvent readyForMore = new AutoResetEvent(true); // Initially signaled
public SetData(MyData d)
{
// This will immediately determine if readyForMore is set or not.
if( readyForMore.WaitOne(0,true) ) {
this.d=d;
e.Set(); // notify that new data is available
}
// you could return a bool or something to indicate it bailed.
}
void Runner() // this runs in separate thread and waits for d to be set to a new value
{
while (true)
{
e.WaitOne(); // waits for new data to process
DoLongOperationWith_d(d);
readyForMore.Set();
}
}
С помощью этого подхода вы можете сделать так, чтобы SetData взял тайм-аут и передал его в WaitOne
, Я думаю, однако, вы должны расследовать ThreadPool.QueueUserWorkItem
,