线程安全递减计数器

Thread Safe decrement counter

问题

在一个项目案例中,我需要创建多个线程,这些线程从队列中挑选任务并 运行 处理它们。如果一组其他任务仍在 运行ning,则其中一些任务无法 运行。考虑像文件复制和碎片整理 (运行s when system is idle) in windows .

解决方案

为了实现这个,我创建了一个 class 基于 System.Threading.CountdownEvent.

每当一个线程从队列中选择一个阻塞任务时,他们将 Increment CounterEvent 并且在他们完成他们的工作后他们将 Decrement CounterEvent

如果一个线程选择一个低优先级任务,它会Wait直到CounterEvent为零然后开始运行ning。

低优先级任务可以立即从 Reset 开始,共 CounterEvent

主线程或并行线程可以通过查询CurrentCount.

来监控锁的状态

代码如下:

using System;
using System.Diagnostics.Contracts;
using System.Threading;


public class CounterEvent : IDisposable {

    private volatile int m_currentCount;
    private volatile bool m_disposed;
    private ManualResetEventSlim m_event;

    // Gets the number of remaining signals required to set the event.
    public int CurrentCount {
        get {
            return m_currentCount;
        }
    }

    // Allocate a thin event, Create a latch in signaled state.
    public CounterEvent() {
        m_currentCount = 0;

        m_event = new ManualResetEventSlim();
        m_event.Set(); // 
    }

    // Decrements the counter. if counter is zero signals other threads to continue
    public void Decrement() {
        ThrowIfDisposed();
        Contract.Assert(m_event != null);
        int newCount = 0;
        if (m_currentCount >= 0) {
            #pragma warning disable 0420
            newCount = Interlocked.Decrement(ref m_currentCount);
            #pragma warning restore 0420
        }
        if (newCount == 0) {
            m_event.Set();
        }
    }

    // increments the current count by one.
    public void Increment() {
        ThrowIfDisposed();
        #pragma warning disable 0420
        Interlocked.Increment(ref m_currentCount);
        #pragma warning restore 0420
    }

    // Resets the CurrentCount to the value of InitialCount.
    public void Reset() {
        ThrowIfDisposed();
        m_currentCount = 0;
        m_event.Set();
    }

    // Blocks the current thread until the System.Threading.CounterEvent is set.
    public void Wait() {
        ThrowIfDisposed();
        m_event.Wait();
    }

    /// <summary>
    /// Throws an exception if the latch has been disposed.
    /// </summary>
    private void ThrowIfDisposed() {
        if (m_disposed) {
            throw new ObjectDisposedException("CounterEvent");
        }
    }

    // According to MSDN this is not thread safe
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // According to MSDN Dispose() is not thread-safe.
    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            m_event.Dispose();
            m_disposed = true;
        }
    }
}

问题

这段代码能按预期工作吗? 有什么我没有看到的缺陷吗? 这样做有没有更好的选择?

备注

应用程序是用 System.Threading.Thread 编写的,对我来说转换它的成本非常高,但是一个很好的替代解决方案总是值得为将来努力。

这应该是一个原子操作,如果你这样做,它不是线程安全的

if (m_currentCount >= 0) 
{
    newCount = Interlocked.Decrement(ref m_currentCount);
}

可能 m_currentCountifInterlocked.Decrement 之间改变 你应该重写你的逻辑来使用 Interlocked.CompareExchange 我也会在你分配给 m_currentCount 的每个地方使用 Interlocked.Exchange 然后你不需要 volatilepragma 您还应该注意,在非常重的负载下,可能会发生重置事件 Set 丢失