首页 文章

使用List的消费者/ 生产环境 者模式

提问于
浏览
0

我有一个WinForms应用程序,有一个消费者和一个 生产环境 者任务 . 我的 生产环境 者任务定期连接到Web服务并检索指定数量的字符串,然后需要将这些字符串放入某种并发固定大小的FIFO队列中 . 我的消费者任务然后处理这些字符串然后作为SMS消息发送(每个消息一个字符串) . 我的 生产环境 者任务调用的SOAP函数需要一个参数来指定我想要获取的字符串数 . 该号码将由我队列中的可用空间决定 . 因此,如果我的最大队列大小为100个字符串,并且队列中有60个字符串,那么下次我的 生产环境 者轮询Web服务时,我需要它来请求40个字符串,因为那时我可以放入队列中的所有内容 .

这是我用来表示我的固定大小FIFO队列的代码:

public class FixedSizeQueue<T>
{
    private readonly List<T> queue = new List<T>();
    private readonly object syncObj = new object();

    public int Size { get; private set; }

    public FixedSizeQueue(int size)
    {
        Size = size;
    }

    public void Enqueue(T obj)
    {
        lock (syncObj)
        {
            queue.Insert(0, obj);

            if (queue.Count > Size)
            {
                queue.RemoveRange(Size, queue.Count - Size);
            }
        }
    }

    public T[] Dequeue()
    {
        lock (syncObj)
        {
            var result = queue.ToArray();
            queue.Clear();
            return result;
        }
    }

    public T Peek()
    {
        lock (syncObj)
        {
            var result = queue[0];
            return result;
        }
    }

    public int GetCount()
    {
        lock (syncObj)
        {
            return queue.Count;
        }
    }

我的 生产环境 者任务当前没有指定我需要从Web服务获得的字符串数量,但似乎它可以像在队列中获取当前项目数(q.GetCount())然后从我的队列中减去它一样简单最大队列大小 . 但是,即使GetCount()使用了锁,也不可能只要GetCount()退出,我的消费者任务就可以处理队列中的10个字符串,这意味着我永远无法真正保持队列100%充分?

此外,我的消费者任务基本上需要"peek"在队列中的第一个字符串,然后尝试在SMS消息中发送它 . 如果消息可以't be sent, I need to leave the string in it'在队列中的原始位置 . 我首先想到的是在队列中的第一个字符串"peek",尝试在SMS消息中发送它,然后如果发送成功则将其从队列中删除 . 这样,如果发送失败,字符串仍然在队列中的原始位置 . 这听起来合理吗?

1 回答

  • 1

    这是一个广泛的问题,所以确实没有确定的答案,但这是我的想法 .

    但是,即使GetCount()使用了锁,也不可能只要GetCount()退出,我的消费者任务就可以处理队列中的10个字符串,这意味着我将永远无法保持队列100 %满?

    是的,除非您在查询到Web服务的整个过程中锁定 syncObj . 但 生产环境 者/消费者的观点是允许消费者在 生产环境 者获取更多东西时处理物品 . 关于这个,你真的没什么可做的;在某些时候,队列将不会100%满 . 如果它总是100%满,则意味着消费者根本没有做任何事情 .

    这样,如果发送失败,字符串仍然在队列中的原始位置 . 这听起来合理吗?

    也许,但是你对这个编码的方式, Dequeue() 操作返回队列的整个状态并清除它 . 给定此接口的唯一选择是重新排队稍后要处理的失败项,这是一种非常合理的技术 .

    我还会考虑为消费者添加一种方法来阻止自己,直到有待处理的项目为止 . 例如:

    public T[] WaitForItemAndDequeue(TimeSpan timeout)
    {
        lock (syncObj) {
            if (queue.Count == 0 && !Monitor.Wait(syncObj, timeout)) {
                return null; // Timeout expired
            }
    
            return Dequeue();
        }
    }
    
    public T[] WaitForItem()
    {
        lock (syncObj) {
            while (queue.Count != 0) {
                Monitor.Wait(syncObj);
            }
    
            return Dequeue();
        }
    }
    

    然后你必须在操作列表后更改 Enqueue() 以调用 Monitor.Pulse(syncObj) (因此在方法结束时,但在 lock 块内) .

相关问题