C#让线程在出列时睡觉?

时间:2009-11-23 08:38:21

标签: c# thread-safety queue

我正在尝试使用WebClient异步下载一堆文件。根据我的理解,这是可能的,但每次下载需要一个WebClient对象。所以我想我只是在我的程序开始时把一堆它们放在一个队列中,然后一次一个地弹出它们并告诉它们下载一个文件。文件下载完成后,可以将它们推回到队列中。

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

lock(queue) {
    queue.Enqueue(webClient);
}

右?但是把它们弹出来呢?我希望我的主线程在队列为空时休眠(等到另一个Web客户端准备就绪,以便它可以开始下一次下载)。我想我可以在队列旁边使用Semaphore来跟踪队列中有多少元素,这会让我的线程在必要时进入休眠状态,但这似乎不是一个很好的解决方案。如果每次推送/弹出我的队列中的某些内容并且它们不同步时我忘记减少/增加我的信号量会发生什么?那会很糟糕。是不是有一些很好的方法让queue.Dequeue()自动睡觉,直到有一个项目出队然后继续?

我也欢迎完全不涉及队列的解决方案。我只想知道一个队列是跟踪哪些WebClient可以使用的最简单方法。

3 个答案:

答案 0 :(得分:5)

以下是使用信号量的示例。 IMO比使用Monitor更清洁:

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();
        }
    }
}

答案 1 :(得分:3)

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

我的线程教程中有simple example of this - 在该页面的一半左右滚动。它是在仿制前编写的,但它应该很容易更新。您可能需要添加各种功能,例如“停止”队列的能力:这通常通过使用一种“空工作项”令牌来执行;你在队列中注入尽可能多的“停止”项目,并且当它们出现一个线程时,每个项目都会停止出列。

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

IIRC,.NET 4.0中有一些类型(作为Parallel Extensions的一部分)会做同样的事情,但更好:)我想你希望BlockingCollection包裹一个{ {3}}

答案 2 :(得分:0)

我使用BlockingQueue来处理这种情况。当队列为空时,你可以调用.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();
    }
}

只需使用它代替普通的队列,它就可以做你需要的。