2009-08-17 79 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