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

时间:2014-07-30 15:42:36

标签: c# multithreading

我有以下代码抛出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);
            }

        }
    }

1 个答案:

答案 0 :(得分:0)

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

您现在拥有的是一个系统,任何时候只允许两个项目在队列中。最初,您的信号量计数为2.每次排队项目时,计数都会减少。在两个项目之后,计数为0,如果您尝试再次发布,您将获得信号量完全异常。

如果您确实希望使用信号量执行此操作,则需要从Release方法中删除Enqueue调用。并向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为池线程分配)的成本。相反,线程在队列上执行非忙等待。您也不必担心显式锁定等问题。代码更简单,更健壮,并且不太可能包含错误。