C#在退出队列时将线程置于睡眠状态?

C#在退出队列时将线程置于睡眠状态?,c#,thread-safety,queue,C#,Thread Safety,Queue,我正在尝试使用异步下载一堆文件。据我所知,这是可能的,但每次下载都需要一个WebClient对象。所以我想我应该在我的程序开始的时候把一堆文件放到队列中,然后一次一个地把它们放出来,让它们下载一个文件。下载完文件后,它们可以被推回到队列中 把东西推到我的队列上应该不会太糟糕,我只需要做如下事情: lock(queue) { queue.Enqueue(webClient); } 对吧??但是把它们脱掉怎么样?我希望我的主线程在队列为空时休眠(等待另一个web客户端准备就绪,以便它可以开

我正在尝试使用异步下载一堆文件。据我所知,这是可能的,但每次下载都需要一个
WebClient
对象。所以我想我应该在我的程序开始的时候把一堆文件放到队列中,然后一次一个地把它们放出来,让它们下载一个文件。下载完文件后,它们可以被推回到队列中

把东西推到我的队列上应该不会太糟糕,我只需要做如下事情:

lock(queue) {
    queue.Enqueue(webClient);
}
对吧??但是把它们脱掉怎么样?我希望我的主线程在队列为空时休眠(等待另一个web客户端准备就绪,以便它可以开始下一次下载)。我想我可以在队列旁边使用一个
信号灯
,来跟踪队列中有多少元素,这会在必要时让我的线程休眠,但这似乎不是一个很好的解决方案。如果每次我在队列上/下推/弹出某个内容时忘记减少/增加信号量,而它们不同步,会发生什么情况?那太糟糕了。有没有什么好办法让
queue.Dequeue()
自动休眠,直到有一个项目要排队,然后继续


我也欢迎完全不需要排队的解决方案。我只是觉得队列是跟踪哪些WebClient已准备好使用的最简单方法。

您需要的是生产者/消费者队列

我在我的线程教程中有一个例子——在该页的一半处滚动。它是在泛型之前编写的,但是应该很容易更新。您可能需要添加各种功能,例如“停止”队列的功能:这通常通过使用某种“空工作项”标记来执行;您在队列中注入的“停止”项的数量与退出线程的数量相同,并且每个线程在遇到一个线程时都停止退出队列

搜索“生产者-消费者队列”可能会为您提供更好的代码示例-这实际上只是演示等待/脉冲


IIRC,在.NET4.0中有一些类型(作为并行扩展的一部分)可以做同样的事情,但要好得多:)我想你需要一个包装a。

下面是一个使用信号量的示例。在IMO中,它比使用监视器清洁得多:

public class BlockingQueue<T>
{
    Queue<T> _queue = new Queue<T>();
    Semaphore _sem = new Semaphore(0, Int32.MaxValue);

    public void Enqueue(T item)
    {
        lock (_queue)
        {
            _queue.Enqueue(item);
        }

        _sem.Release();
    }

    public T Dequeue()
    {
        _sem.WaitOne();

        lock (_queue)
        {
            return _queue.Dequeue();
        }
    }
}
公共类阻止队列
{
队列_Queue=新队列();
信号量_sem=新信号量(0,Int32.MaxValue);
公共无效排队(T项)
{
锁定(_队列)
{
_排队。排队(项目);
}
_sem.Release();
}
公共T出列()
{
_sem.WaitOne();
锁定(_队列)
{
return_queue.Dequeue();
}
}
}

我使用阻塞队列来处理这种情况。当队列为空时,您可以调用.Dequeue,调用线程只需等待,直到有东西要出列

public class BlockingQueue<T> : IEnumerable<T>
{
    private int _count = 0;
    private Queue<T> _queue = new Queue<T>();

    public T Dequeue()
    {
        lock (_queue)
        {
            while (_count <= 0)
                Monitor.Wait(_queue);
            _count--;
            return _queue.Dequeue();
        }
    }

    public void Enqueue(T data)
    {
        if (data == null)
            throw new ArgumentNullException("data");
        lock (_queue)
        {
            _queue.Enqueue(data);
            _count++;
            Monitor.Pulse(_queue);
        }
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        while (true)
            yield return Dequeue();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<T>) this).GetEnumerator();
    }
}
公共类阻止队列:IEnumerable
{
私有整数_计数=0;
专用队列_Queue=新队列();
公共T出列()
{
锁定(_队列)
{

while(_count为什么需要while循环
Monitor.Wait(listLock);
?消费者不应该只有在有商品准备好消费时才被叫醒吗?+1.另一个很好的线程教程,有生产者-消费者队列,就是这个-(只需在生产者上查找)@马克:如果你没有while循环,消费者可能会被叫醒,但另一个消费者可能会在叫醒之前过来抢走商品。等待呼叫应该几乎总是有一个while循环来测试某些条件。是的,你可以(不幸的是IMO)锁定任何对象-但我认为这是一个坏主意。这意味着您不知道其他代码将锁定它。队列是否锁定了自身?如果锁定了,会发生什么?在这种情况下可能没问题……但我发现,如果我保留单独的私有锁,更容易考虑。它不会忙着等待-它不只是“稍后再检查”;当监视器脉冲时,线程会被唤醒。至于监视器的行为。等等——如果你要编写自己的线程集合,你真的应该在开始之前理解这些东西。一旦你理解了,这一点都不成问题。@Mark:这并不是忙着以任何实际的方式等待。是的,理论上来说,这是可能的r线程在“偷”所需的准确时间进入每一次都是下一项——但你认为现实生活中这种情况发生的频率有多高?有什么理由使用你自己的计数而不是询问队列吗?永远不要使用监视器。脉冲,总是有更好的选择。@erikkallen:这是一个大胆的说法,没有给出任何理由。在这种情况下,对我来说似乎是合理的——你会怎么做你的做法不同吗?反正你不需要一个单独的计数器变量-队列保持它自己的计数。是的……我也是这么想的。只是觉得奇怪,这不是一个“标准”类。我可能会使用此解决方案,谢谢!@Jon:它保持自己的计数,是的,但是获取计数不是线程安全的,是吗?因此,您必须锁定队列以获取计数,然后如果计数为0,释放它,等等,再次抓取锁……它只是变得更糟糕,不是吗?如果您在访问queu时已经拥有锁e(在我的示例中是这样的)那么您就没事了。我的版本可以正常工作,但没有单独的计数器变量。如果队列有自己的计数,则为True(不确定为什么使用监视器的另一个答案声明显式计数)我仍然认为对于这个特殊的问题,信号灯比监视器要优雅一点,但我想这是主观的。