C#producer / consumer / observer?

时间:2010-12-16 15:54:43

标签: c# asp.net multithreading event-handling producer-consumer

我有一个生产者/消费者队列,除了有特定类型的对象。因此,不只是任何消费者都可以使用添加的对象。我不想为每种类型创建一个特定的队列,因为有太多。 (它有点延伸了生产者/消费者的定义,但我不确定正确的术语是什么。)

EventWaitHandle是否允许带参数的脉冲?例如myHandle.Set(AddedType = "foo")。现在我正在使用Monitor.Wait,然后每个消费者都会检查脉冲是否真的是针对他们的,但这似乎毫无意义。

我现在拥有的pseduocode版本:

class MyWorker {
    public string MyType {get; set;}
    public static Dictionary<string, MyInfo> data;

    public static void DoWork(){
        while(true){
             if(Monitor.Wait(data, timeout)){
                   if (data.ContainsKey(MyType)){
                        // OK, do work
                   }
             }
        }
    }
}

正如你所看到的,当其他东西被添加到字典中时,我可能会得到脉冲。我只关心将MyType添加到dict中。有没有办法做到这一点?这不是什么大不了的事,但是,例如,我现在必须手动处理超时,因为每次锁定都可以在超时内成功,但MyType永远不会添加到timeout内的dict中。

2 个答案:

答案 0 :(得分:3)

这是一个有趣的问题。听起来这个解决方案的关键是priority queue的阻塞变体。 Java有PriorityBlockingQueue,但不幸的是,.NET BCL的等价物是不存在的。但是,一旦有了,实施就很容易了。

class MyWorker 
{
    public string MyType {get; set;}
    public static PriorityBlockingQueue<string, MyInfo> data; 

    public static void DoWork()
    {
        while(true)
        {
            MyInfo value;
            if (data.TryTake(MyType, timeout, out value))
            {
                // OK, do work
            }
        }
    }
}

实施PriorityBlockingQueue并不是非常困难。通过使用BlockingCollectionAdd样式方法,使用与Take相同的模式,我提出了以下代码。

public class PriorityBlockingQueue<TKey, TValue>
{
    private SortedDictionary<TKey, TValue> m_Dictionary = new SortedDictionary<TKey,TValue>();

    public void Add(TKey key, TValue value)
    {
        lock (m_Dictionary)
        {
            m_Dictionary.Add(key, value);
            Monitor.Pulse(m_Dictionary);
        }
    }

    public TValue Take(TKey key)
    {
        TValue value;
        TryTake(key, TimeSpan.FromTicks(long.MaxValue), out value);
        return value;
    }

    public bool TryTake(TKey key, TimeSpan timeout, out TValue value)
    {
        value = default(TValue);
        DateTime initial = DateTime.UtcNow;
        lock (m_Dictionary)
        {
            while (!m_Dictionary.TryGetValue(key, out value))
            {
                if (m_Dictionary.Count > 0) Monitor.Pulse(m_Dictionary); // Important!
                TimeSpan span = timeout - (DateTime.UtcNow - initial);
                if (!Monitor.Wait(m_Dictionary, span))
                {
                    return false;
                }
            }
            m_Dictionary.Remove(key);
            return true;
        }
    }
}

这是一个快速实施,它有几个问题。首先,我根本没有测试过它。其次,它使用红黑树(通过SortedDictionary)作为底层数据结构。这意味着TryTake方法将具有O(log(n))复杂度。优先级队列通常具有O(1)删除复杂性。优先级队列选择的典型数据结构是heap,但我发现实际上skip lists实际上更好,原因有几个。 .NET BCL中不存在这些,这就是我使用SortedDictionary而不是在这种情况下性能较差的原因。

我应该在这里指出,这实际上并没有解决无意义的Wait/Pulse行为。它只是封装在PriorityBlockingQueue类中。但是,至少这肯定会清理代码的核心部分。

您的代码看起来并不像每个键处理多个对象,但在添加到字典时,使用Queue<MyInfo>而不是普通的旧MyInfo可以轻松添加。

答案 1 :(得分:1)

您似乎希望将生产者/消费者队列与Observer模式相结合 - 通用消费者线程或线程从队列中读取,然后将事件传递给所需的代码。在这种情况下,您实际上不会发信号通知观察者,而只是在消费者线程识别出对给定工作项感兴趣的人时调用它。

.Net中的观察者模式通常使用C#事件实现。您只需要调用该对象的事件处理程序,并通过它调用一个或多个观察者。目标代码首先必须通过将自己添加到事件中来自己注册观察对象,以便在工作到达时通知。