28

也许是太晚了,但我想不出一个好的方法来做到这一点。

我已经开始了一堆异步下载,我想等到它们全部完成后再程序终止。这让我相信我应该在下载开始时增加一些东西,并在下载完成时减少它。但是,我该如何等到计数再次为 0?

信号量以相反的方式工作,因为当没有可用资源时阻塞,而不是在它们可用时阻塞(当计数为 0 时阻塞,而不是非零)。

4

8 回答 8

17

查看本杂志文章中的 CountdownLatch 类。

更新:自 4.0 版以来,框架现已涵盖CountdownEvent 类

于 2009-12-27T12:13:22.923 回答
10

在 .NET 4 中有一种特殊类型用于此目的CountdownEvent

或者您可以像这样自己构建类似的东西:

const int workItemsCount = 10;
// Set remaining work items count to initial work items count
int remainingWorkItems = workItemsCount;

using (var countDownEvent = new ManualResetEvent(false))
{
    for (int i = 0; i < workItemsCount; i++)
    {
        ThreadPool.QueueUserWorkItem(delegate
                                        {
                                            // Work item body
                                            // At the end signal event
                                            if (Interlocked.Decrement(ref remainingWorkItems) == 0)
                                                countDownEvent.Set();
                                        });
    }
    // Wait for all work items to complete
    countDownEvent.WaitOne();
}
于 2009-12-27T10:06:07.260 回答
8

看起来System.Threading.WaitHandle.WaitAll可能非常适合:

等待指定数组中的所有元素接收到信号。

于 2009-12-27T09:33:08.613 回答
4

好吧...您可以抢回主线程上的所有信号量计数器,以便在 count 为 0 而不是 non-zero 时阻塞

修订:在这里我假设了 3 件事:

  • 在程序运行时,可能随时开始新的下载作业。
  • 退出程序后,将不再有需要处理的新下载。
  • 退出程序时,您需要等待所有文件完成下载

所以这是我的解决方案,已修改:

使用足够大的计数器初始化信号量,这样您就不会达到最大值(根据您的情况,它可能只是 100 或仅 10):

var maxDownloads = 1000;
_semaphore = new Semaphore(0, maxDownloads);

然后在每次下载时,在开始下载之前从 WaitOne() 开始,以便在程序退出的情况下,无法开始下载。

if (_semaphore.WaitOne())
    /* proceeds with downloads */
else
    /* we're terminating */

然后在下载完成时,释放一个计数器(如果我们已经获得了一个):

finally { _semaphore.Release(1); }

然后在“退出”事件中,消耗掉 Semaphore 上的所有计数器

for (var i = 0; i < maxDownloads; i++)
    _semaphore.WaitOne();

// all downloads are finished by this point.

...

于 2009-12-27T09:28:20.417 回答
3

我有一个类似的问题,我需要在某些事件上重置服务器,但必须等待所有打开的请求完成才能杀死它。

我在服务器启动时使用CountdownEvent 类将其初始化为 1,并且在我执行的每个请求中:

try
{
    counter.AddCount();
    //do request stuff
}
finally
{
    counter.Signal();
}

并且在收到 ResetEvent 后,我​​向计数器发出一次信号以消除起始 1,并等待实时请求发出信号,表明它们已完成。

void OnResetEvent()
{
    counter.Signal();
    counter.Wait();
    ResetServer();
    //counter.Reset(); //if you want to reset everything again.
}

基本上,您用一个初始化 CountdownEvent,使其处于非信号状态,并且随着每个 AddCount 调用您正在增加计数器,并且随着每个 Signal 调用您正在减少它,始终保持在 1 以上。在您的等待线程中,您首先发出信号它一次将初始值 1 减为 0,如果没有线程在运行 Wail() 将立即停止阻塞,但如果还有其他线程仍在运行,则等待线程将等待,直到它们发出信号。注意,一旦计数器达到 0,所有后续的 AddCount 调用都会抛出异常,您需要先重置计数器。

于 2018-01-15T15:41:56.267 回答
0

对于每个线程,您启动 Interlock.Increment 一个计数器。对于线程完成时的每个回调,将其递减。

然后使用 Thread.Sleep(10) 或其他方法进行循环,直到计数达到零。

于 2009-12-27T09:30:41.270 回答
0

这是我的 CountdownLatch 的 C# 2.0 实现:

public class CountdownLatch
{
    private int m_count;
    private EventWaitHandle m_waitHandle = new EventWaitHandle(true, EventResetMode.ManualReset);

    public CountdownLatch()
    {
    }

    public void Increment()
    {
        int count = Interlocked.Increment(ref m_count);
        if (count == 1)
        {
            m_waitHandle.Reset();
        }
    }

    public void Add(int value)
    {
        int count = Interlocked.Add(ref m_count, value);
        if (count == value)
        {
            m_waitHandle.Reset();
        }
    }

    public void Decrement()
    {
        int count = Interlocked.Decrement(ref m_count);
        if (m_count == 0)
        {
            m_waitHandle.Set();
        }
        else if (count < 0)
        {
            throw new InvalidOperationException("Count must be greater than or equal to 0");
        }
    }

    public void WaitUntilZero()
    {
        m_waitHandle.WaitOne();
    }
}
于 2016-11-21T12:14:07.967 回答
0

根据这里的建议,这就是我想出的。除了等到计数为 0 之外,如果您产生太多线程(计数 > 最大值),它会休眠。警告:这没有经过全面测试。

public class ThreadCounter
{
    #region Variables
    private int currentCount, maxCount;
    private ManualResetEvent eqZeroEvent;
    private object instanceLock = new object();
    #endregion

    #region Properties
    public int CurrentCount
    {
        get
        {
            return currentCount;
        }
        set
        {
            lock (instanceLock)
            {
                currentCount = value;
                AdjustZeroEvent();
                AdjustMaxEvent();
            }
        }
    }

    public int MaxCount
    {
        get
        {
            return maxCount;
        }
        set
        {
            lock (instanceLock)
            {
                maxCount = value;
                AdjustMaxEvent();
            }
        }
    }
    #endregion

    #region Constructors
    public ThreadCounter() : this(0) { }
    public ThreadCounter(int initialCount) : this(initialCount, int.MaxValue) { }
    public ThreadCounter(int initialCount, int maximumCount)
    {
        currentCount = initialCount;
        maxCount = maximumCount;
        eqZeroEvent = currentCount == 0 ? new ManualResetEvent(true) : new ManualResetEvent(false);
    }
    #endregion

    #region Public Methods
    public void Increment()
    {
        ++CurrentCount;
    }

    public void Decrement()
    {
        --CurrentCount;
    }

    public void WaitUntilZero()
    {
        eqZeroEvent.WaitOne();
    }
    #endregion

    #region Private Methods
    private void AdjustZeroEvent()
    {
        if (currentCount == 0) eqZeroEvent.Set();
        else eqZeroEvent.Reset();
    }

    private void AdjustMaxEvent()
    {
        if (currentCount <= maxCount) Monitor.Pulse(instanceLock);
        else do { Monitor.Wait(instanceLock); } while (currentCount > maxCount);
    }
    #endregion
}
于 2016-11-21T16:31:45.010 回答