Search code examples
c#multithreadinglockingthread-safetyautoresetevent

C# Threading issue with AutoResetEvent


How to properly synchronize this? At the moment it is possible that SetData is called after e.WaitOne() has completed so d could be already set to another value. I tried to insert locks but it resulted into 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);
   }
}

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


Solution

  • This is untested, but is an elegant way to do this with the .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); 
            }
        }
    }
    

    Example program:

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