C# 生产者/消费者/观察者?
我有一个生产者/消费者队列,除了有特定类型的对象。因此,不仅仅是任何消费者都可以使用添加的对象。我不想为每种类型创建一个特定的队列,因为队列太多了。(这在某种程度上延伸了生产者/消费者的定义,但我不确定正确的术语是什么。) 有没有像EventWaitHandle这样的东西,它允许带有参数的脉冲?e、 g.C# 生产者/消费者/观察者?,c#,asp.net,multithreading,event-handling,producer-consumer,C#,Asp.net,Multithreading,Event Handling,Producer Consumer,我有一个生产者/消费者队列,除了有特定类型的对象。因此,不仅仅是任何消费者都可以使用添加的对象。我不想为每种类型创建一个特定的队列,因为队列太多了。(这在某种程度上延伸了生产者/消费者的定义,但我不确定正确的术语是什么。) 有没有像EventWaitHandle这样的东西,它允许带有参数的脉冲?e、 g.myHandle.Set(AddedType=“foo”)。现在我正在使用监视器。等待,然后每个消费者检查脉冲是否确实是为他们准备的,但这似乎毫无意义 我现在拥有的psedoocode版本: c
myHandle.Set(AddedType=“foo”)
。现在我正在使用监视器。等待,然后每个消费者检查脉冲是否确实是为他们准备的,但这似乎毫无意义
我现在拥有的psedoocode版本:
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
}
}
}
}
}
类MyWorker{
公共字符串MyType{get;set;}
公共静态字典数据;
公共静态无效工作(){
while(true){
if(监视器等待(数据、超时)){
if(data.ContainsKey(MyType)){
//好的,开始工作吧
}
}
}
}
}
正如你所看到的,当其他东西被添加到dict时,我可能会得到脉冲。我只关心MyType被添加到dict时。有什么方法可以做到这一点吗?这不是什么大问题,但是,例如,我现在必须手动处理超时,因为每次获取锁都可能在超时内成功,但是MyType
永远不会在timeout
内添加到dict中。您似乎希望将生产者/消费者队列与观察者模式相结合-通用消费者线程或从队列中读取的线程,然后将事件传递给所需的代码。在本例中,您实际上不会向观察者发出信号,而是在使用者线程确定谁对给定的工作项感兴趣时调用它
Net中的观察者模式通常使用C#事件实现。您只需要调用对象的事件处理程序,就可以通过它调用一个或多个观察者。目标代码首先必须通过将自身添加到事件中,以便在工作到达时发出通知,从而向观察对象注册自身。这是一个有趣的问题。听起来解决方案的关键是一个阻塞变量。Java具有优先级BlockingQueue
,但不幸的是,与.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
}
}
}
}
这是一个快速的实现,它有几个问题。首先,我根本没有测试过它。其次,它使用红黑树(viaSortedDictionary
)作为底层数据结构。这意味着TryTake
方法将具有O(log(n))复杂性。优先级队列通常具有O(1)删除复杂性。优先级队列通常选择的数据结构是a,但我发现,由于几个原因,在实践中这种结构实际上更好。这两个都不存在于.NET BCL中,这就是为什么我使用了SortedDictionary
,尽管在这种情况下它的性能较差
我应该在这里指出,这实际上并不能解决无意义的Wait/Pulse
行为。它只是封装在PriorityBlockingQueue
类中。但是,至少这肯定会清理代码的核心部分
这看起来不像你的代码在每个键上处理多个对象,但是在添加到字典时,使用队列而不是普通的MyInfo
可以很容易地添加。这只是一个想法,每个对象类型都有自己的锁对象,这样你就可以监视.Pulse
合适的对象了吗?@deltreme:是的,这是可能的,但我不想为每种类型创建一个新对象。我不确定您所说的类型太多而无法为每种类型创建单独的队列是什么意思。为什么呢这是一种很自然的方式,我不确定有很多单独的队列怎么会比任何其他事件处理机制效率低。@Isaac:用户说“我想要X”。X涉及以生产者/消费者有意义的方式聚合一些数据。然而,没有人可能会再次请求X,因此仅为X设置一个队列是毫无意义的,而且可能是不可能的,因为我们无法预测用户想要什么。你可以认为它等同于事件如何获取参数——即使有一种通用类型的事件(“制作人制造了什么”),我们也希望以不同的方式处理这一事件。这是怎么回事:每当有脉冲,它就会激活下一个消费者从睡眠中醒来。现在,他可以寻找
有什么用,如果不是他喜欢的类型,他可以回去睡觉,让其他人轮到他。如果这不影响任何性能,我认为这是一个简单的方法。这听起来像我应该有一个字典
,并且在类型
更新时只做监视.Pulse(Dictionary[type])
?看起来我可以移除观测者,然后从制作者那里手动完成。(我同意它会起作用,但我希望我可以使用后台消费者集合。)如果您使用Monitor
激活目标对象,那么通过队列访问单独的消费者线程肯定是多余的。封装这一点很有意思,很好。您使用SortedDictionary
而不仅仅是字典
有什么原因吗?只是更快的插入时间?@Xodarap:好问题。我最初认为您需要一个不接受密钥的Take
重载,它将从字典中删除第一项。不过,你需要一本分类词典才能正确地做到这一点。这实际上就是优先级队列的工作方式。我想您可以使用一个普通的旧字典,只需调用包装类BlockingDictionary
。接得好。
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;
}
}
}