C# 线程和队列
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/794249/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C# Threading and Queues
提问by neouser99
This isn't about the different methods I could or should be using to utilize the queues in the best manner, rather something I have seen happening that makes no sense to me.
这不是关于我可以或应该使用的以最佳方式利用队列的不同方法,而是我所看到的对我来说毫无意义的事情。
void Runner() {
// member variable
queue = Queue.Synchronized(new Queue());
while (true) {
if (0 < queue.Count) {
queue.Dequeue();
}
}
}
This is run in a single thread:
这是在单个线程中运行的:
var t = new Thread(Runner);
t.IsBackground = true;
t.Start();
Other events are "Enqueue"ing else where. What I've seen happen is over a period of time, the Dequeue will actually throw InvalidOperationException, queue empty. This should be impossible seeing as how the count guarantees there is something there, and I'm positive that nothing else is "Dequeue"ing.
其他事件在其他地方“入队”。我所看到的情况是经过一段时间后,出队实际上会抛出 InvalidOperationException,队列为空。这应该是不可能的,因为计数如何保证那里有东西,而且我肯定没有其他东西是“出队”的。
The question(s):
问题:
- Is it possible that the Enqueue actually increases the count before the item is fully on the queue (whatever that means...)?
- Is it possible that the thread is somehow restarting (expiring, reseting...) at the Dequeue statement, but immediately after it already removed an item?
- Enqueue 是否有可能在项目完全在队列中之前增加计数(不管这意味着......)?
- 线程是否有可能在 Dequeue 语句处以某种方式重新启动(到期、重置...),但在它已经删除了一个项目之后立即启动?
Edit (clarification):
编辑(澄清):
These code pieces are part of a Wrapper class that implements the background helper thread. The Dequeue here is the only Dequeue, and all Enqueue/Dequeue are on the Synchronized member variable (queue).
这些代码片段是实现后台帮助线程的 Wrapper 类的一部分。这里的Dequeue是唯一的Dequeue,所有的Enqueue/Dequeue都在Synchronized成员变量(队列)上。
采纳答案by Erich Mirabal
Using Reflector, you can see that no, the count does not get increased until after the item is added.
使用 Reflector,您可以看到不,在添加项目之前,计数不会增加。
As Ben points out, it does seem as you do have multiple people calling dequeue.
正如 Ben 指出的那样,看起来确实有很多人在调用 dequeue。
You say you are positive that nothing else is calling dequeue. Is that because you only have the one thread calling dequeue? Is dequeue called anywhere else at all?
你说你肯定没有其他东西叫出队。那是因为你只有一个线程调用 dequeue 吗?出队是否在其他任何地方被调用?
EDIT:
编辑:
I wrote a little sample code, but could not get the problem to reproduce. It just kept running and running without any exceptions.
我写了一些示例代码,但无法重现问题。它只是一直运行和运行,没有任何异常。
How long was it running before you got errors? Maybe you can share a bit more of the code.
在出现错误之前它运行了多长时间?也许你可以分享更多的代码。
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;
}
}
回答by Ben S
Here is what I think the problematic sequence is:
这是我认为有问题的顺序是:
(0 < queue.Count)
evaluates to true, the queue is not empty.- This thread gets preemptedand another thread runs.
- The other thread removes an item from the queue, emptying it.
- This thread resumes execution, but is now within the if block, and attempts to dequeue an empty list.
(0 < queue.Count)
评估为真,队列不为空。- 这个线程被抢占,另一个线程运行。
- 另一个线程从队列中删除一个项目,清空它。
- 该线程恢复执行,但现在在 if 块中,并尝试使空列表出列。
However, you say nothing else is dequeuing...
然而,你说没有别的东西出列......
Try outputting the count inside the if block. If you see the count jump numbers downwards, someone else is dequeuing.
尝试在 if 块内输出计数。如果您看到计数向下跳跃数字,则其他人正在出列。
回答by Mike
Here's a possible answer from the MSDN pageon this topic:
以下是MSDN 页面上关于此主题的可能答案:
Enumerating through a collection is intrinsically not a thread-safe procedure. Even when a collection is synchronized, other threads can still modify the collection, which causes the enumerator to throw an exception. To guarantee thread safety during enumeration, you can either lock the collection during the entire enumeration or catch the exceptions resulting from changes made by other threads.
通过集合进行枚举本质上不是线程安全的过程。即使在同步集合时,其他线程仍然可以修改集合,这会导致枚举器抛出异常。为了保证枚举期间的线程安全,您可以在整个枚举期间锁定集合或捕获其他线程所做更改导致的异常。
My guess is that you're correct - at some point, there's a race condition happening, and you end up dequeuing something that isn't there.
我的猜测是你是对的——在某些时候,发生了竞争条件,你最终将一些不存在的东西出列。
A Mutex or Monitor.Lock is probably appropriate here.
Mutex 或 Monitor.Lock 可能适合这里。
Good luck!
祝你好运!
回答by Ryan
Are the other areas that are "Enqueuing" data also using the same synchronized queue object? In order for the Queue.Synchronized to be thread-safe, all Enqueue and Dequeue operations must use the same synchronized queue object.
其他“排队”数据的区域是否也使用相同的同步队列对象?为了使 Queue.Synchronized 成为线程安全的,所有入队和出队操作必须使用相同的同步队列对象。
From MSDN:
从MSDN:
To guarantee the thread safety of the Queue, all operations must be done through this wrapper only.
为了保证 Queue 的线程安全,所有的操作只能通过这个包装器来完成。
Edited: If you are looping over many items that involve heavy computation or if you are using a long-term thread loop (communications, etc.), you should consider having a wait function such as System.Threading.Thread.Sleep, System.Threading.WaitHandle.WaitOne, System.Threading.WaitHandle.WaitAll, or System.Threading.WaitHandle.WaitAnyin the loop, otherwise it might kill system performance.
编辑:如果您正在循环涉及大量计算的许多项目,或者如果您正在使用长期线程循环(通信等),您应该考虑使用等待函数,例如System.Threading.Thread.Sleep,System.Threading.Sleep 。 Threading.WaitHandle.WaitOne、System.Threading.WaitHandle.WaitAll或System.Threading.WaitHandle.WaitAny在循环中,否则可能会影响系统性能。
回答by tofi9
question 1: If you're using a synchronized queue, then: no, you're safe! But you'll need to use the synchronized instance on both sides, the supplier and the feeder.
问题 1:如果您使用的是同步队列,那么:不,您很安全!但是您需要在供应商和供料器双方使用同步实例。
question 2: Terminating your worker thread when there is no work to do, is a simple job. However, you either way need a monitoring thread or have the queue start a background worker thread whenever the queue has something to do. The last one sounds more like the ActiveObject Pattern, than a simple queue (which's Single-Responsibily-Pattern says that it should only do queueing).
问题 2:在没有工作要做时终止您的工作线程是一项简单的工作。但是,无论何时,您都需要一个监视线程或让队列在队列有事情要做时启动后台工作线程。最后一个听起来更像是 ActiveObject 模式,而不是一个简单的队列(Single-Responsibily-Pattern 说它应该只进行排队)。
In addition, I'd go for a blocking queue instead of your code above. The way your code works requires CPU processing power even if there is no work to do. A blocking queue lets your worker thread sleep whenever there is nothing to do. You can have multiple sleeping threads running without using CPU processing power.
另外,我会使用阻塞队列而不是上面的代码。即使没有工作要做,您的代码的工作方式也需要 CPU 处理能力。阻塞队列让您的工作线程在无事可做时休眠。您可以在不使用 CPU 处理能力的情况下运行多个休眠线程。
C# doesn't come with a blocking queue implementation, but there a many out there. See this exampleand this one.