2015-10-09 18 views
1

我想要實現下列數據類型在C#實現併發安全的自定義數據類型的

public class MyType 
{ 
    void Set(int i); 
    void AddHandler(int i, Action action); 
} 

語義如下。

  1. 兩種方法都必須是安全的併發。
  2. 'i'的最大值已知,相對較低(〜100)。
  3. 試圖設置我不止一次應該失敗。
  4. 具有價值的調用集我應該調用所有處理程序註冊該我。
  5. AddHandler爲給定的i註冊新的處理程序。如果我已經設置好,立即調用行動。

例如,考慮以下序列

Set(1) 
Set(2) 
AddHandler(3, f1) 
AddHandler(3, f2) 
Set(1)   // Fails, 1 is already set 
AddHandler(2, g) // g is called as 2 is already set 
Set(3)   // f1, f2 are called 
AddHandler(3, h) // h is called as 3 is now set 

目標是最小化所需要的每個方法調用來完成分配。這是我嘗試實現它的代碼。

public class MyType 
{ 
    const int N = 10; 
    static readonly Action[] s_emptyHandler = new Action[0]; 

    readonly bool[] m_vars = new bool[N]; 
    readonly List<Action>[] m_handlers = new List<Action>[N]; 

    public void Set(int i) 
    { 
     Action[] handlers; 

     lock (this) 
     { 
      if (m_vars[i]) throw new InvalidOperationException(); 
      m_vars[i] = true; 
      handlers = m_handlers[i] != null ? m_handlers[i].ToArray() : s_emptyHandler; 
     } 

     foreach (var action in handlers) 
      action(); 
    } 

    public void AddHandler(int i, Action action) 
    { 
     var done = false; 

     lock (this) 
     { 
      if (m_vars[i]) 
       done = true; 
      else 
      { 
       if(m_handlers[i] == null) 
        m_handlers[i] = new List<Action>(); 

       m_handlers[i].Add(action); 
      } 
     } 

     if (done) 
      action(); 
    } 
} 
+1

告訴我們你的代碼,你試圖去做 – Backs

+1

應爲'鎖(myDictionary)一樣簡單{...}' - 只記得不調用鎖內的動作 - 取而代之的是當前的觀察者並在之後調用它們 - 如果你想使用併發收集的東西 – Carsten

+0

謝謝。我編輯了這個問題。關於第二條評論,是的,我有類似的想法。但是這需要我快照當前的一組處理程序。是否有任何併發​​的收集可以幫助您避免它? – Suyog

回答

1

以陣列快照在每個Set方法是無效的。另一方面,由於您需要額外的同步,因此使用BlockingCollection沒有任何意義。對於你的情況,一些不變的集合會更好。
甚至有一個簡單的方法利用了您僅添加處理程序的事實。我們可以使用帶有計數字段對的顯式數組而不是列表類,因此我們需要在Set方法中執行的操作是在受保護的塊內採用數組引用和計數值。然後我們可以安全地迭代數組來計數和調用處理程序。下面是使用所描述的方法代碼:

public class MyType 
{ 
    struct Entry 
    { 
     public bool IsSet; 
     public int HandlerCount; 
     public Action[] HandlerList; 
     public void Add(Action handler) 
     { 
      if (HandlerList == null) HandlerList = new Action[4]; 
      else if (HandlerList.Length == HandlerCount) Array.Resize(ref HandlerList, 2 * HandlerCount); 
      HandlerList[HandlerCount++] = handler; 
     } 
    } 
    const int N = 10; 
    readonly Entry[] entries = new Entry[N]; 
    readonly object syncLock = new object(); 
    public void Set(int index) 
    { 
     int handlerCount; 
     Action[] handlerList; 
     lock (syncLock) 
     { 
      if (entries[index].IsSet) throw new InvalidOperationException(); 
      entries[index].IsSet = true; 
      handlerCount = entries[index].HandlerCount; 
      handlerList = entries[index].HandlerList; 
     } 
     for (int i = 0; i < handlerCount; i++) 
      handlerList[i](); 
    } 
    public void AddHandler(int index, Action handler) 
    { 
     if (handler == null) throw new ArgumentException("handler"); 
     lock (syncLock) 
     { 
      entries[index].Add(handler); 
      if (!entries[index].IsSet) return; 
     } 
     handler(); 
    } 
} 
+0

因此,在調整大小時枚舉數組是安全的嗎?似乎可能,但只是想確認 – Suyog

+0

絕對。 Resize是一個幫助器方法,它創建一個新的數組,複製現有的並安全地替換傳遞的舊數組引用 - 您可以在http://referencesource.microsoft.com/#mscorlib/system/array.cs,71074deaf111c4e3中看到。 –

1
public class MyType 
    { 
     private HashSet<int> set = new HashSet<int>(); 
     private Dictionary<int, BlockingCollection<Action>> actions = new Dictionary<int, BlockingCollection<Action>>(); 

     private void ExecuteActions(BlockingCollection<Action> toExecute) 
     { 
      Task.Run(() => 
      { 
       while (!toExecute.IsCompleted) 
       { 
        try 
        { 
         Action action = toExecute.Take(); 
         action(); 
        } 
        catch { } 
       } 
      }); 
     } 

     public void Set(int i) 
     { 
      lock (this) 
      { 
       if (!set.Contains(i)) 
       { 
        set.Add(i); 

        BlockingCollection<Action> toExecute; 
        if (!actions.TryGetValue(i, out toExecute)) 
        { 
         actions[i] = toExecute = new BlockingCollection<Action>(); 
        } 

        ExecuteActions(toExecute); 
       } 
      } 
     } 


     public void AddHandler(int i, Action action) 
     { 
      lock (this) 
      { 
       BlockingCollection<Action> toExecute; 
       if (!actions.TryGetValue(i, out toExecute)) 
       { 
        actions[i] = toExecute = new BlockingCollection<Action>(); 
       } 

       toExecute.Add(action); 
      } 
     } 
    } 
+0

謝謝。請參閱更新。這種方法(以及我能想到的方法)需要獲取我希望避免的陣列快照。 – Suyog

+0

我不知道「陣列快照」是什麼意思...... –

+0

如果我們不想在鎖內執行處理程序(出於顯而易見的原因),那麼您需要克隆列表'toExecute',以便您可以遍歷它在鎖外。 – Suyog