使用另一个线程处理对状态的只读访问的更好方法是什么?

时间:2011-05-04 00:33:53

标签: c# multithreading locking

这是一个设计问题,而不是错误修复问题。

情况就是这样。我有很多集合和对象包含在一个类中。它们的内容仅由单个消息处理程序线程更改。还有一个正在进行渲染的线程。它迭代通过其中一些集合的每个帧,并根据这些对象的值绘制到屏幕。它不会以任何方式改变对象,它只是读取它们的值。

现在渲染完成后,如果更改了任何集合,渲染方法中的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);
            }
        }
    }
}

这一切都很好,但如果有更好的方法,我宁愿不把锁放在我的所有州集合上。这是“正确”的方式,还是有更好的方法?

2 个答案:

答案 0 :(得分: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();
    }
}

答案 1 :(得分:0)

嗯......你试过ReaderWriterLockSlim了吗?用其中一个包含每个conllection,并确保每次访问时都启动读取或写入操作。