繁体   English   中英

使用列表的消费者/生产者模式

[英]Consumer/producer pattern using a List

我有一个WinForms应用程序,其中有一个使用者和一个生产者任务。 我的生产者任务定期连接到Web服务,并检索指定数目的字符串,然后将它们放入某种并发的固定大小FIFO队列中。 然后,我的使用者任务处理这些字符串,然后以SMS消息的形式发送出去(每条消息一个字符串)。 生产者任务调用的SOAP函数需要一个参数来指定我要获取的字符串数。 该数字将由我队列中的可用空间确定。 因此,如果我的最大队列大小为100个字符串,并且下一次我的生产者轮询Web服务时队列中有60个字符串,那么我需要它来请求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%充分?

同样,我的使用者任务基本上需要先“窥视”队列中的第一个字符串,然后再尝试通过SMS消息发送它。 如果无法发送消息,则需要将字符串保留在队列中的原始位置。 我首先想到的是“窥视”队列中的第一个字符串,尝试通过SMS消息发送它,如果发送成功, 则将其从队列中删除。 这样,如果发送失败,则字符串仍在队列中的原始位置。 听起来合理吗?

这是一个广泛的问题,因此确实没有确定的答案,但这是我的想法。

但是,即使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块内部)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM