简体   繁体   English

C#与AutoResetEvent的线程问题

[英]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. 目前,在e.WaitOne()完成后可能会调用SetData因此d可能已经设置为另一个值。 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? 最好的解决方案是将一个新的布尔变量dataAlreadyBeenSetAndWaitingToBeProcessedSetData设置为true,并且在DoLongOperationWith_d结束时它可以设置为true,所以如果调用SetData并将此变量设置为true,它可能只返回?

This is untested, but is an elegant way to do this with the .net based primitives: 这是未经测试的,但使用基于.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); 
        }
    }
}

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

There are two possibly troubling scenarios here. 这里有两个可能令人不安的场景。

1: 1:

  • DoLongOperationWith_d(d) finishes. DoLongOperationWith_d(d)结束。
  • SetData() is called, storing a new value in d. 调用SetData(),在d中存储新值。
  • e.WaitOne() is called, but since a value has already been set the thread waits forever. 调用e.WaitOne(),但由于已经设置了一个值,因此线程将永远等待。

If that's your concern, I think you can relax. 如果这是你的担忧,我想你可以放松一下。 From the documentation , we see that 文档中 ,我们看到了

If a thread calls WaitOne while the AutoResetEvent is in the signaled state, the thread does not block. 如果线程在AutoResetEvent处于信号状态时调用WaitOne,则线程不会阻塞。 The AutoResetEvent releases the thread immediately and returns to the non-signaled state. AutoResetEvent立即释放线程并返回到非信号状态。

So that's not a problem. 所以这不是问题。 However, depending on how and when SetData() is called, you may be dealing with the more serious 但是,根据调用SetData()的方式和时间,您可能会处理更严重的问题

2: 2:

  • SetData() is called, storing a new value in d and waking up the runner. 调用SetData(),在d中存储新值并唤醒运行器。
  • DoLongOperationWith_d(d) starts. DoLongOperationWith_d(d)开始。
  • SetData() is called again, storing a new value in d. 再次调用SetData(),在d中存储新值。
  • SetData() is called again! 再次调用SetData()! The old value of d is lost forever; d的旧价值永远消失; DoLongOperationWith_d() will never be invoked upon it. 永远不会调用DoLongOperationWith_d()。

If that's your problem, the simplest way to solve it is a concurrent queue. 如果这是你的问题,解决它的最简单方法是并发队列。 Implementations abound. 实施比比皆是。

You can use 2 events, 你可以使用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();
     }
}

One of the things you can do with this approach is have SetData take a timeout, and pass that into WaitOne . 使用此方法可以执行的操作之一是让SetData超时,并将其传递给WaitOne I think however you shoudl investigate ThreadPool.QueueUserWorkItem . 不过我想你会调查ThreadPool.QueueUserWorkItem

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM