线程安全递减计数器

时间:2015-10-29 15:38:51

标签: c# multithreading .net-4.0

问题

在项目案例中,我需要创建多个线程,这些线程从队列中挑选任务并运行它们。如果一组其他任务仍在运行,则其中一些任务无法运行。在Windows中考虑文件复制和碎片整理(在系统空闲时运行)等内容。

解决方案

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

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

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

低优先级广告可以立即从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编写的,为我转换它的成本非常高,但是一个很好的替代解决方案总是值得为将来工作。

1 个答案:

答案 0 :(得分:1)

This should be one atomic operation and it is not threadsafe if you do it like this

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

It may happen that m_currentCount is changed between the if and the Interlocked.Decrement You should rewrite your logic to use Interlocked.CompareExchange I would also use Interlocked.Exchange in every place where you assign to m_currentCount then you don´t need volatile and the pragma You should also be aware of that under very heavy load it can happen that a reset event Set is getting lost