为什么这个c#代码会抛出SemaphoreFullException ?
本文关键字:SemaphoreFullException 代码 为什么 | 更新日期: 2023-09-27 18:15:34
我有以下代码抛出SemaphoreFullException
,我不明白为什么?
如果我把_semaphore = new SemaphoreSlim(0, 2)
改成
_semaphore = new SemaphoreSlim(0, int.MaxValue)
则一切正常。谁能找到这个代码的错误并解释给我听?
class BlockingQueue<T>
{
private Queue<T> _queue = new Queue<T>();
private SemaphoreSlim _semaphore = new SemaphoreSlim(0, 2);
public void Enqueue(T data)
{
if (data == null) throw new ArgumentNullException("data");
lock (_queue)
{
_queue.Enqueue(data);
}
_semaphore.Release();
}
public T Dequeue()
{
_semaphore.Wait();
lock (_queue)
{
return _queue.Dequeue();
}
}
}
public class Test
{
private static BlockingQueue<string> _bq = new BlockingQueue<string>();
public static void Main()
{
for (int i = 0; i < 100; i++)
{
_bq.Enqueue("item-" + i);
}
for (int i = 0; i < 5; i++)
{
Thread t = new Thread(Produce);
t.Start();
}
for (int i = 0; i < 100; i++)
{
Thread t = new Thread(Consume);
t.Start();
}
Console.ReadLine();
}
private static Random _random = new Random();
private static void Produce()
{
while (true)
{
_bq.Enqueue("item-" + _random.Next());
Thread.Sleep(2000);
}
}
private static void Consume()
{
while (true)
{
Console.WriteLine("Consumed-" + _bq.Dequeue());
Thread.Sleep(1000);
}
}
}
如果您想使用信号量来控制并发线程的数量,那么您使用它是错误的。当你从队列中取出一个项目时,你应该获取这个信号量,当线程处理完这个项目时,你应该释放这个信号量。
你现在拥有的是一个系统,它只允许两个项目在任何时候都在队列中。最初,你的信号量计数为2。每次您排队一个项目,计数就会减少。在两个项目之后,计数为0,如果你试图再次释放,你将得到一个信号量满异常。
如果你真的想用一个信号量来做这件事,你需要从Enqueue
方法中删除Release
调用。并将Release
方法添加到BlockingQueue
类中。然后你会写:
private static void Consume()
{
while (true)
{
Console.WriteLine("Consumed-" + _bq.Dequeue());
Thread.Sleep(1000);
bq.Release();
}
}
这将使您的代码工作,但它不是一个很好的解决方案。更好的解决方案是使用BlockingCollection<T>
和两个持久消费者。比如:
private BlockingCollection<int> bq = new BlockingCollection<int>();
void Test()
{
// create two consumers
var c1 = new Thread(Consume);
var c2 = new Thread(Consume);
c1.Start();
c2.Start();
// produce
for (var i = 0; i < 100; ++i)
{
bq.Add(i);
}
bq.CompleteAdding();
c1.Join();
c2.Join();
}
void Consume()
{
foreach (var i in bq.GetConsumingEnumerable())
{
Console.WriteLine("Consumed-" + i);
Thread.Sleep(1000);
}
}
为您提供了两个持久线程来消费这些项。这样做的好处是避免了为每个项目启动一个新线程(或让RTL分配一个池线程)的成本。相反,线程在队列上执行非繁忙的等待。您也不必担心显式锁定等问题。代码更简单,更健壮,并且包含错误的可能性更小。