为什么这个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);
            }
        }
    }

为什么这个c#代码会抛出SemaphoreFullException ?

如果您想使用信号量来控制并发线程的数量,那么您使用它是错误的。当你从队列中取出一个项目时,你应该获取这个信号量,当线程处理完这个项目时,你应该释放这个信号量。

你现在拥有的是一个系统,它只允许两个项目在任何时候都在队列中。最初,你的信号量计数为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分配一个池线程)的成本。相反,线程在队列上执行非繁忙的等待。您也不必担心显式锁定等问题。代码更简单,更健壮,并且包含错误的可能性更小。

相关文章:
  • 没有找到相关文章