C# производитель / потребитель / наблюдатель?
у меня есть производитель / потребитель очереди, за исключением того, что есть определенные типы объектов. Таким образом, не только любой потребитель может потреблять добавленный объект. Я не хочу создавать определенную очередь для каждого типа, так как их слишком много. (Это своего рода растягивает определение производителя/потребителя, но я не уверен, что это правильный термин.)
есть ли такая вещь, как 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 добавляется в дикт. Есть ли способ сделать это? Это не огромное дело, но, например, я должен вручную обрабатывать тайм-ауты сейчас, потому что каждый get блокировки может преуспеть в течение тайм-аута, но MyType
не добавлены дикт внутри timeout
.
2 ответов
это интересный вопрос. Похоже, что ключом к решению является блокирующий вариант a приоритет в очереди. 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
это не очень сложно. Следуя той же схеме, что и BlockingCollection
С помощью Add
и 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). Обычно для очередей приоритетов выбирается структура данных кучу, но я считаю, что пропустить списки на самом деле лучше в практики по нескольким причинам. Ни один из них не существует в .NET BCL, поэтому я использовал SortedDictionary
вместо этого, несмотря на его низкую производительность в этом сценарии.
я должен указать здесь, что это на самом деле не решает бессмысленное Wait/Pulse
поведение. Он просто инкапсулирован в PriorityBlockingQueue
класса. Но, по крайней мере, это, безусловно, очистит основную часть вашего кода.
не похоже, что ваш код обрабатывал несколько объектов на ключ, но это было бы легко добавить с помощью Queue<MyInfo>
вместо обычного MyInfo
при добавлении в словарь.
похоже, вы хотите объединить очередь производителя/потребителя с шаблоном наблюдателя - общий поток потребителя или потоки считываются из очереди, а затем передают событие требуемому коду. В этом случае вы фактически не сигнализируете наблюдателю, а просто вызываете его, когда поток потребителя определяет, кто заинтересован в данном рабочем элементе.
шаблон наблюдателя в .Net обычно реализуется с использованием событий C#. Вам просто нужно будет вызвать обработчик событий для объект и один или несколько наблюдателей будут вызваны через него. Целевой код сначала должен был бы зарегистрироваться с наблюдаемым объектом, добавив себя к событию для уведомления о прибытии работы.