2012-07-25 107 views
3

我試圖實現一個類,它使用一個簡單的緩存來保存從內部服務檢索到的數據。我使用ManualResetEvent來阻塞多個線程,這些線程可能會嘗試在第一個線程同時刷新緩存的數據,以通過調用Set(),然後調用Reset()來成功地通知其他人繼續進行數據檢索。在測試時我注意到有時候所有的線程都被釋放了,有時候有一個或者更多的線程不會被超時,就好像我在所有的線程被釋放之前調用Reset一樣。有人能解釋我做錯了什麼嗎?ManualResetEvent問題不會一直釋放所有等待的線程

我已經包含下面的代碼的減少版本。

private bool _updating; 
    private const int WaitTimeout = 20000; 
    private DateTime _lastRefresh; 
    private object _cacheData; 
    private readonly ManualResetEvent _signaller = new ManualResetEvent(false); 

private void RefreshCachedData() 
     { 
      Console.WriteLine("ThreadId {0}: Refreshing Cache", Thread.CurrentThread.ManagedThreadId); 
     if (_updating) 
     { 
      Console.WriteLine("ThreadId {0}: Cache Refresh in progress, waiting on signal.", Thread.CurrentThread.ManagedThreadId); 

      // another thread is currently updating the cache so wait for a signal to continue 
      if (!_signaller.WaitOne(WaitTimeout)) 
       Console.WriteLine("ThreadId {0}: Thread timed out ({1}s) waiting for a signal that the cache had been refreshed", 
        Thread.CurrentThread.ManagedThreadId,WaitTimeout); 

      Console.WriteLine("ThreadId {0}: Signal recieved to use refreshed cache.", Thread.CurrentThread.ManagedThreadId); 
     } 
     else 
     { 
      try 
      { 
       _updating = true; 

       var result = _requestHandler.GetNewData(); 

       if (!result.Success) 
       { 
         Console.WriteLine("Failed to retrieve new data."); 
       } 
       else 
       { 
        // switch the cache with the new data 
        _cacheData = result.Data; 

        Console.WriteLine(
         "ThreadId {0}: Cache refresh success.", 
         Thread.CurrentThread.ManagedThreadId); 
        Thread.Sleep(8000); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Error occured: {0}", ex); 
      } 
      finally 
      { 
       // Set the refresh date time regardless of whether we succeded or not 
       _lastRefresh = DateTime.Now; 
       _updating = false; 

       // signal any other threads to to carry on and use the refreshed cache 
       Console.WriteLine("ThreadId {0}: Signalling other threads that cache is refreshed.", Thread.CurrentThread.ManagedThreadId); 
       _signaller.Set(); 
       _signaller.Reset(); 
      } 
     } 
    } 
+0

'的Thread.Sleep(8000) '和'WaitOne'聽起來好像不會很好地播放。 – leppie 2012-07-25 09:27:04

+0

線程睡眠只是爲了測試的目的,以確保所有其他線程進入等待線程刷新緩存釋放他們之前 – gouldos 2012-07-25 09:30:26

+1

這可能是一個紅色的鯡魚,但如果我把一個線程睡眠50ms之間的集並重置,我還沒有看到在測試任何線程不會被釋放。 – gouldos 2012-07-25 09:50:19

回答

2

看起來像你的線程沒有得到重置之前從ResetEvent釋放。

您可以通過創建事件打開並讓第一個線程輸入您的方法重置它來解決問題。

或者,你可以做這樣的事情避免ManualResetEvent的行爲反覆無常:

private object _latch = new object(); 
private bool _updating; 

private void UpdateIfRequired() 
{ 
    lock (_latch) 
    { 
     if (_updating) 
     { 
      //wait here and short circuit out when the work is done 
      while (_updating) 
       Monitor.Wait(_latch); 

      return; 
     } 

     _updating = true; 
    } 

    //do lots of expensive work here 

    lock (_latch) 
    { 
     _updating = false; 
     Monitor.PulseAll(_latch); //let the other threads go 
    } 
} 

查看此頁面爲一個偉大的解釋,這是如何工作http://www.albahari.com/threading/part4.aspx#_Signaling_with_Wait_and_Pulse