2012-01-26 2 views
0

Я пытаюсь создать список подписки. Давайте рассмотрим пример:Лучшая структура данных для потокового списка подписки?

список издателей, каждый из которых имеет список журналов, каждый из которых имеет список абонентов

Publishers -> Журналы -> Подписчики

Имеет смысл использовать из словаря в словаре в словаре в C#. Можно ли это сделать без блокировки всей структуры при добавлении/удалении абонента без условий гонки?

Также код очень быстро запутался в C#, что заставляет меня думать, что я не пойду по правильному пути. Есть ли более простой способ сделать это? Вот конструктор и метод подписки:

Примечания: Код использует Источник, Тип Абонент вместо имен выше

Источника ---> Тип ---> Абонента

public class SubscriptionCollection<SourceT, TypeT, SubscriberT> 
{ 
// Race conditions here I'm sure! Not locking anything yet but should revisit at some point 

ConcurrentDictionary<SourceT, ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>>> SourceTypeSubs; 

public SubscriptionCollection() 
{ 
    SourceTypeSubs = new ConcurrentDictionary<SourceT, ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>>>(); 
} 

public void Subscribe(SourceT sourceT, TypeT typeT, SubscriberT subT) { 

    ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>> typesANDsubs; 
    if (SourceTypeSubs.TryGetValue(sourceT, out typesANDsubs)) 
    { 
     ConcurrentDictionary<SubscriberT, SubscriptionInfo> subs; 
     if (typesANDsubs.TryGetValue(typeT, out subs)) 
     { 

      SubscriptionInfo subInfo; 
      if (subs.TryGetValue(subT, out subInfo)) 
      { 
       // Subscription already exists - do nothing 

      } 
      else 
      { 
       subs.TryAdd(subT, new SubscriptionInfo()); 
      } 
     } 
     else 
     { 
      // This type does not exist - first add type, then subscription 
      var newType = new ConcurrentDictionary<SubscriberT, SubscriptionInfo>(); 
      newType.TryAdd(subT, new SubscriptionInfo()); 
      typesANDsubs.TryAdd(typeT, newType); 

     } 

    } 
    else 
    { 
     // this source does not exist - first add source, then type, then subscriptions 
     var newSource = new ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>>(); 
     var newType = new ConcurrentDictionary<SubscriberT, SubscriptionInfo>(); 
     newType.TryAdd(subT, new SubscriptionInfo()); 
     newSource.TryAdd(typeT, newType); 
     SourceTypeSubs.TryAdd(sourceT, newSource); 
    }; 
} 
+0

вопрос C# -специфический, или вы ищете подход, который может быть использован в любом месте? – svick

+0

Anywhere really .. тогда я могу адаптировать его к C# –

+0

Я спрашиваю, потому что, если вопрос является C# -специфичным, то есть классы непосредственно в .Net framework, которые вы могли бы использовать. – svick

ответ

1

If вы используете ConcurrentDictionary, как и вы, вам не нужна блокировка, о которой уже позаботились.

Но вам все равно придется думать о состоянии гонки и о том, как с ними бороться. К счастью, ConcurrentDictionary дает вам именно то, что вам нужно. Например, если у вас есть два потока, оба пытаются подписаться на источник, который еще не существует, только один из них будет успешным. Но вот почему TryAdd() возвращает, было ли добавление успешным. Вы не можете просто игнорировать его возвращаемое значение. Если он возвращает false, вы знаете, что в какой-то другой поток уже добавлен этот источник, поэтому теперь вы можете получить словарь.

Другой вариант - использовать the GetOrAdd() method. Он извлекает уже существующее значение и создает его, если он еще не существует.

Я хотел бы переписать код, как это (и сделать это гораздо проще, по пути):

public void Subscribe(SourceT sourceT, TypeT typeT, SubscriberT subT) 
{ 
    var typesAndSubs = SourceTypeSubs.GetOrAdd(sourceT, 
     _ => new ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>>()); 

    var subs = typesAndSubs.GetOrAdd(typeT, 
     _ => new ConcurrentDictionary<SubscriberT, SubscriptionInfo>()); 

    subs.GetOrAdd(subT, _ => new SubscriptionInfo()); 
} 
+0

GetOrAdd делает код намного более чистым, так как мне не нужно проверять возвращаемое значение TryAdd и повторять попытку (возможно, несколько раз?), Чтобы гарантировать, что каждый поток успешно завершится. Активизация в настоящее время будет выбирать как принятый ответ, если ничего не произойдет. Большое спасибо!! –

+0

@HarryMexican, вам не нужно делать 'TryAdd()' несколько раз. Это может произойти только в том случае, если кто-то еще добавил элемент с тем же ключом. – svick

Смежные вопросы