C # Threading problem with AutoResetEvent

How to sync this correctly? It is currently possible that SetData is called after the completion of e.WaitOne() , so d may already be set to a different value. I tried to insert the locks, but this led to a deadlock.

 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); } } 

The best solution would be to introduce a new boolean variable dataAlreadyBeenSetAndWaitingToBeProcessed , which is set to SetData in true, and at the end of DoLongOperationWith_d it can be set to true, so if SetData is called with this variable is set to true, can it just return?

+4
source share
3 answers

This is untested, but it is an elegant way to do this with .net based primitives:

 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); } } } 

Program Example:

 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); } } 

This is a non-queued version, a queue version may be preferred:

 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; } } } 
+3
source

There are two possible problem scenarios here.

1

  • DoLongOperationWith_d (d) ends.
  • SetData () is called, storing the new value in d. Called
  • e.WaitOne (), but since the value is already set, the thread waits forever.

If this is your concern, I think you can relax. From the documentation we see that

If a thread calls WaitOne and AutoResetEvent is in a signal state, the thread is not blocked. AutoResetEvent immediately releases the stream and returns to the no-signal state.

So this is not a problem. However, depending on how and when SetData () is called, you may have to deal with more serious

2

  • SetData () is called, storing the new value in d and waking up the runner.
  • DoLongOperationWith_d (d) is launched.
  • SetData () is called again, storing the new value in d.
  • SetData () is called again! The old value of d is lost forever; DoLongOperationWith_d () will never be called on it.

If your problem, the easiest way to solve it is to use a parallel queue. Implementations abound.

+2
source

You can use 2 events,

 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(); } } 

One of the things you can do with this approach is to force SetData to time out and pass this to WaitOne . I think, however, you should explore ThreadPool.QueueUserWorkItem .

+1
source

All Articles