C#线程和队列

时间:2009-04-27 16:23:16

标签: c# multithreading

这不是关于我能够或应该以最佳方式利用队列的不同方法,而是我所看到的对我来说毫无意义的事情。

void Runner() {
    // member variable
    queue = Queue.Synchronized(new Queue());
    while (true) {
        if (0 < queue.Count) {
            queue.Dequeue();
        }
    }
}

这是在一个线程中运行的:

var t = new Thread(Runner);
t.IsBackground = true;
t.Start();

其他事件是“排队”其他地方。我所见到的是在一段时间内,Dequeue实际上会抛出InvalidOperationException,队列为空。这应该是不可能看到计数如何保证那里有东西,而且我很肯定没有别的东西是“出列”。

问题:

  1. Enqueue是否有可能在项目完全进入队列之前实际增加计数(无论这意味着什么......)?
  2. 线程是否有可能在Dequeue语句中以某种方式重新启动(到期,重置...),但是在它已经删除了一个项目之后立即?
  3. 修改(澄清):

    这些代码片段是实现后台帮助程序线程的Wrapper类的一部分。此处的Dequeue是唯一的Dequeue,所有Enqueue / Dequeue都在Synchronized成员变量(队列)上。

5 个答案:

答案 0 :(得分:11)

使用Reflector,您可以看到不,在添加项目之前,计数不会增加。

正如本指出的那样,看起来确实有多人打电话。

你说你是肯定的,没有别的东西叫出队。是因为你只有一个线程调用dequeue?是否在任何其他地方都被叫出去?

编辑:

我写了一些示例代码,但无法重现问题。它只是一直运行和运行,没有任何例外。

在出错之前它运行了多长时间?也许你可以分享一些代码。

class Program
{
    static Queue q = Queue.Synchronized(new Queue());
    static bool running = true;

    static void Main()
    {
        Thread producer1 = new Thread(() =>
            {
                while (running)
                {
                    q.Enqueue(Guid.NewGuid());
                    Thread.Sleep(100);
                }
            });

        Thread producer2 = new Thread(() =>
        {
            while (running)
            {
                q.Enqueue(Guid.NewGuid());
                Thread.Sleep(25);
            }
        });

        Thread consumer = new Thread(() =>
            {
                while (running)
                {
                    if (q.Count > 0)
                    {
                        Guid g = (Guid)q.Dequeue();
                        Console.Write(g.ToString() + " ");
                    }
                    else
                    {
                        Console.Write(" . ");
                    }
                    Thread.Sleep(1);
                }
            });
        consumer.IsBackground = true;

        consumer.Start();
        producer1.Start();
        producer2.Start();

        Console.ReadLine();

        running = false;
    }
}

答案 1 :(得分:3)

以下是我认为有问题的序列:

  1. (0 < queue.Count)的计算结果为true,队列不为空。
  2. 此线程获取preempted并运行另一个线程。
  3. 另一个线程从队列中删除一个项目,清空它。
  4. 此线程恢复执行,但现在位于if块内,并尝试将空列表出列。
  5. 但是,你说别的什么东西都没有出现......

    尝试输出if块内的计数。如果您看到计数跳跃数字向下,则其他人正在出局。

答案 2 :(得分:3)

以下是the MSDN page关于此主题的可能答案:

  

通过集合枚举是   本质上不是线程安全的   程序。即使是收藏品   同步,其他线程仍然可以   修改集合,这会导致   枚举器抛出异常。   确保螺纹安全   枚举,你可以锁定   整个收集   枚举或捕获异常   由其他人做出的改变   线程。

我的猜测是你是对的 - 在某些时候,会出现竞争状况,你最终会出现一些不存在的东西。

Mutex或Monitor.Lock在这里可能是合适的。

祝你好运!

答案 3 :(得分:2)

“排队”数据的其他区域是否也使用相同的同步队列对象?为了使Queue.Synchronized成为线程安全的,所有Enqueue和Dequeue操作都必须使用相同的同步队列对象。

来自MSDN

  

保证线程安全   队列,必须完成所有操作   通过这个包装器。

编辑: 如果你循环遍历许多涉及大量计算的项目或者如果你正在使用长期线程循环(通信等),你应该考虑使用等待函数,例如System.Threading.Thread.SleepSystem.Threading.WaitHandle.WaitOneSystem.Threading.WaitHandle.WaitAll,否则可能会导致系统性能下降。

答案 4 :(得分:1)

问题1:如果你使用的是同步队列,那么:不,你是安全的!但是您需要在供应商和供料器两侧使用同步实例。

问题2:在没有工作要做的时候终止你的工作线程是一项简单的工作。但是,无论哪种方式都需要监视线程,或者只要队列有事可做,队列就会启动后台工作线程。最后一个听起来更像是ActiveObject模式,而不是一个简单的队列(Single-Responsibily-Pattern表示它应该只排队)。

另外,我会使用阻塞队列而不是上面的代码。即使没有工作要做,代码的工作方式也需要CPU处理能力。阻塞队列允许您的工作线程在无事可做时休眠。您可以在不使用CPU处理能力的情况下运行多个休眠线程。

C#没有阻塞队列实现,但那里有很多。请参阅此example和此one