2009-08-17 98 views
4

如何正确地同步这个?目前有可能SetDatae.WaitOne()已完成之后调用,因此d可能已被设置为另一个值。我试图插入锁,但它导致了一个死锁。C#线程与AutoResetEvent问题

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设置为true,并在DoLongOperationWith_d结束它可以被设置为true,所以如果SetData被调用,这个变量设置为true它可能会返回?

回答

3

这是未经测试,但是这是一个优雅的方式来做到这一点。净基于原语:

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

@Spencer Ruport:什么?如果挂起设置为true,则第一次调用SetData时,它将第二次抛出。我敢肯定,有一些办法可以打破这种情况,但我认为这不符合你所描述的顺序。 – Sean 2009-08-17 23:43:43

+0

但this.d不能​​设置,除非挂起是假的。 – 2009-08-17 23:44:16

+0

我的不好。我没有在那里看到'if(pending)'。 – 2009-08-17 23:46:25

2

这里有两种可能令人不安的情况。

1:

  • DoLongOperationWith_d(d)完成。
  • SetData()被调用,将一个新的值存储在d中。
  • e.WaitOne()被调用,但由于已经设置了一个值,所以线程会一直等待。

如果这是您的问题,我认为您可以放松。从documentation,我们看到

如果一个线程在AutoResetEvent处于信号状态时调用WaitOne,则线程不会阻塞。 AutoResetEvent立即释放该线程并返回到非信号状态。

所以这不是问题。然而,这取决于如何以及何时的SetData()被调用时,你可能会较严重

2处理:

  • 的SetData()被调用时,d存储新值,并唤醒亚军。
  • DoLongOperationWith_d(d)开始。
  • SetData()再次被调用,在d中存储一个新的值。
  • SetData()被再次调用! d的旧价值永远消失; DoLongOperationWith_d()永远不会被调用。

如果这是您的问题,解决它的最简单方法是并发队列。实现方式比比皆是。

1

可以使用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

+0

这样做的麻烦是SetData一旦开始处理就会被阻塞。 – 2009-08-18 06:55:49

+0

队列显然优越。我试图完全按照要求回答这个问题。 – 2009-08-18 13:22:36