2011-05-04 38 views
2

這是一個設計問題,而不是錯誤修復問題。更好的方式來處理只讀訪問狀態與另一個線程?

情況是這樣的。我有很多集合和對象包含在一個類中。它們的內容僅由單個消息處理程序線程更改。還有一個正在渲染的線程。每幀都會遍歷這些集合中的一些,並根據這些對象的值繪製到屏幕上。它不會以任何方式改變對象,它只是讀取它們的值。

現在,當渲染完成時,如果任何集合被更改,則渲染方法中的foreach循環會失敗。我應該如何使這個線程安全?編輯:所以我必須鎖定我運行的每個foreach循環之外的集合。這有效,但它似乎有很多重複代碼來解決這個問題。

作爲短,人爲的例子:

class State 
{ 
    public object LockObjects; 
    public List<object> Objects; 

    // Called by message handler thread 
    void HandleMessage() 
    { 
     lock (LockObjects) 
     { 
      Objects.Add(new object()); 
     } 
    } 
} 

class Renderer 
{ 
    State m_state; 

    // Called by rendering thread 
    void Render() 
    { 
     lock (m_state.LockObjects) 
     { 
      foreach (var obj in m_state.Objects) 
      { 
       DrawObject(obj); 
      } 
     } 
    } 
} 

這一切都很好,但我寧願不把門鎖上我所有的收藏狀態,如果有一個更好的辦法。這是「正確的」做法還是有更好的方法?

回答

3

更好的方法是使用開始/結束方法和分隔列表爲您的兩個線程和同步使用自動事件,例如。這將是無鎖的,以你的消息處理線程,使您能夠有很多的渲染/消息處理線程:

class State : IDisposable 
{ 
    private List<object> _objects; 

    private ReaderWriterLockSlim _locker; 

    private object _cacheLocker; 
    private List<object> _objectsCache; 

    private Thread _synchronizeThread; 
    private AutoResetEvent _synchronizationEvent; 
    private bool _abortThreadToken; 

    public State() 
    { 
     _objects = new List<object>(); 
     _objectsCache = new List<object>(); 

     _cacheLocker = new object(); 
     _locker = new ReaderWriterLockSlim(); 

     _synchronizationEvent = new AutoResetEvent(false); 

     _abortThreadToken = false; 

     _synchronizeThread = new Thread(Synchronize); 
     _synchronizeThread.Start(); 
    } 


    private void Synchronize() 
    { 
     while (!_abortThreadToken) 
     { 
      _synchronizationEvent.WaitOne(); 

      int objectsCacheCount; 
      lock (_cacheLocker) 
      { 
       objectsCacheCount = _objectsCache.Count; 
      } 

      if (objectsCacheCount > 0) 
      { 
       _locker.EnterWriteLock(); 

       lock (_cacheLocker) 
       { 
        _objects.AddRange(_objectsCache); 
        _objectsCache.Clear(); 
       } 

       _locker.ExitWriteLock(); 
      } 
     } 
    } 

    public IEnumerator<object> GetEnumerator() 
    { 
     _locker.EnterReadLock(); 

     foreach (var o in _objects) 
     { 
      yield return o; 
     } 

     _locker.ExitReadLock(); 
    } 

    // Called by message handler thread 
    public void HandleMessage() 
    { 
     lock (_cacheLocker) 
     { 
      _objectsCache.Add(new object()); 
     } 

     _synchronizationEvent.Set(); 
    } 

    public void Dispose() 
    { 
     _abortThreadToken = true; 
     _synchronizationEvent.Set(); 
    } 
} 

或(更簡單的方式),可以使用ReaderWriteerLockSlim(或只是鎖定,如果你相信你只有一個閱讀器)如下面的代碼:

class State 
{ 
    List<object> m_objects = new List<object>(); 

    ReaderWriterLockSlim locker = new ReaderWriterLockSlim(); 
    public IEnumerator<object> GetEnumerator() 
    { 
     locker.EnterReadLock(); 

     foreach (var o in Objects) 
     { 
      yield return o; 
     } 

     locker.ExitReadLock(); 
    } 

    private List<object> Objects 
    { 
     get { return m_objects; } 
     set { m_objects = value; } 
    } 

    // Called by message handler thread 
    public void HandleMessage() 
    { 
     locker.EnterWriteLock(); 

     Objects.Add(new object()); 

     locker.ExitWriteLock(); 
    } 
} 
+0

沒有鎖,我會(偶爾)得到「InvalidOperationException - 集合被修改;枚舉操作可能不會執行。」關於「in」關鍵字。 – Philip 2011-05-04 00:45:31

+0

您將得到與您當前的代碼(使用該鎖)相同的錯誤。嘗試以高併發模式運行它。 – oxilumin 2011-05-04 00:47:13

+0

你說得對,我的鎖需要在foreach循環之外。對於我所做的例子來說,我已經對它進行了編輯。 – Philip 2011-05-04 00:49:27

0

嗯......你有沒有試過ReaderWriterLockSlim?用其中之一包圍每一個conllection,並確保每次訪問時都要啓動讀取或寫入操作。

相關問題