2009-10-22 81 views
38

說我有這個簡單的方法:C#:我怎樣才能使IEnumerable <T>線程安全?

public IEnumerable<uint> GetNumbers() 
{ 
    uint n = 0; 
    while(n < 100) 
     yield return n++; 
} 

你將如何讓這個線程安全的?我的意思是,你會得到一次枚舉,並有多個線程處理所有的數字,沒有任何人得到重複。

我想一個鎖需要在某處使用,但必須在哪裏鎖定一個迭代器塊以保證線程安全?一般來說,你需要記住,如果你想要一個線程安全IEnumerable<T>?或者說,我猜這將是一個線程安全IEnumerator<T> ...?

+4

(以防萬一你沒有檢查我的意見)我只是在這個博客:http://msmvps.com/blogs/jon_skeet/archive/2009/10/23/iterating-atomically.aspx – 2009-10-23 21:22:07

+2

哦, 謝謝!當有人評論你的評論或類似的東西時,這個網站應該有Facebook的通知...嘿嘿。 – Svish 2009-10-24 11:07:02

回答

38

這樣做存在固有的問題,因爲IEnumerator<T>同時具有MoveNext()Current。你真的想要一個單一的電話,如:

bool TryMoveNext(out T value) 

在這一點上,你可以原子移動到下一個元素,並得到一個值。實現這一點,仍然能夠使用yield可能會很棘手......但我會考慮它。我認爲你需要將線程安全的「非線程安全」迭代器包裝成原子執行MoveNext()Current來實現上面顯示的界面。我不知道你怎麼會那麼包裝這個接口回IEnumerator<T>,這樣你可以在foreach雖然使用它...

如果你使用.NET 4.0,並行擴展可以能夠幫助你 - 你需要解釋更多關於你想要做的事情。

這是一個有趣的話題 - 我可能會在博客上寫下它...

編輯:我現在blogged about it有兩種方法。

+3

你應該調用'TryMoveNext'來匹配框架中的類似項目。 :) – 2009-10-22 08:36:25

+0

@ 280Z28:好主意。將編輯:) – 2009-10-22 08:39:50

+0

我能想到的一種方式將允許您在foreach中使用它將有一個類實現IEnumerable,並在內部存儲當前項在threadlocal字段,這樣,當一個線程已成功調用MoveNext將其值存儲起來供以後使用,與其他線程分開。 – 2009-10-22 08:52:23

1

我假設你需要一個保存線程的枚舉器,所以你應該實現那個。

-2

你可以每次只返回一個完整的序列,而不是使用yield:

return Enumerable.Range(0, 100).Cast<uint>().ToArray();

+0

他不想在每個線程中處理完整範圍。 – Guillaume 2009-10-22 08:46:32

+0

而且我也不想將整個範圍放入一個數組或類似的東西。 – Svish 2009-10-22 09:09:43

0

好了,我不知道,但也許在主叫一些鎖嗎?

草案:

Monitor.Enter(syncRoot); 
foreach (var item in enumerable) 
{ 
    Monitor.Exit(syncRoot); 
    //Do something with item 
    Monitor.Enter(syncRoot); 
} 
Monitor.Exit(syncRoot); 
0

我在想,你不能讓yield關鍵字線程安全的,除非你把它依賴於數值已經線程安全來源:

public interface IThreadSafeEnumerator<T> 
{ 
    void Reset(); 
    bool TryMoveNext(out T value); 
} 

public class ThreadSafeUIntEnumerator : IThreadSafeEnumerator<uint>, IEnumerable<uint> 
{ 
    readonly object sync = new object(); 

    uint n; 

    #region IThreadSafeEnumerator<uint> Members 
    public void Reset() 
    { 
     lock (sync) 
     { 
      n = 0; 
     } 
    } 

    public bool TryMoveNext(out uint value) 
    { 
     bool success = false; 

     lock (sync) 
     { 
      if (n < 100) 
      { 
       value = n++; 
       success = true; 
      } 
      else 
      { 
       value = uint.MaxValue; 
      } 
     } 

     return success; 
    } 
    #endregion 
    #region IEnumerable<uint> Members 
    public IEnumerator<uint> GetEnumerator() 
    { 
     //Reset(); // depends on what behaviour you want 
     uint value; 
     while (TryMoveNext(out value)) 
     { 
      yield return value; 
     } 
    } 
    #endregion 
    #region IEnumerable Members 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     //Reset(); // depends on what behaviour you want 
     uint value; 
     while (TryMoveNext(out value)) 
     { 
      yield return value; 
     } 
    } 
    #endregion 
} 

您必須決定是否每個典型的枚舉器啓動都應該重置序列,或者客戶端代碼是否必須這樣做。

1

我只是測試該位的代碼:)

static IEnumerable<int> getNums() 
{ 
    Console.WriteLine("IENUM - ENTER"); 

    for (int i = 0; i < 10; i++) 
    { 
     Console.WriteLine(i); 
     yield return i; 
    } 

    Console.WriteLine("IENUM - EXIT"); 
} 

static IEnumerable<int> getNums2() 
{ 
    try 
    { 
     Console.WriteLine("IENUM - ENTER"); 

     for (int i = 0; i < 10; i++) 
     { 
      Console.WriteLine(i); 
      yield return i; 
     } 
    } 
    finally 
    { 
     Console.WriteLine("IENUM - EXIT"); 
    } 
} 

getNums2(總是調用的代碼的最後一部分。如果你想讓你的IEnumerable成爲線程安全的,添加你想要的線程鎖而不是寫入線,使用ReaderWriterSlimLock,Semaphore,Monitor等等。