我有一个带有一个消费者和一个生产者任务的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%满了?
此外,我的消费者任务基本上需要在尝试通过SMS消息发送之前“查看”队列中的第一个字符串。如果无法发送消息,我需要将字符串保留在队列中的原始位置。我首先考虑实现这个目的是“窥视”队列中的第一个字符串,尝试通过SMS消息发送它,然后然后如果发送成功则将其从队列中删除。这样,如果发送失败,字符串仍然在队列中的原始位置。这听起来合理吗?
答案 0 :(得分: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
块内部。)