2012-07-25 42 views
2

根據Albahari brothers[Page No.273],使用IEnumerable因爲:確實使用IEnumerable接口減少併發問題

通過定義一個單一的方法返回一個枚舉IEnumerable提供了這樣的靈活性

- >迭代邏輯可framed off to another class明白了

- >Moreover it means that several consumers can enumerate the collection at once without interfering with each other不理解

我無法理解第二點!

IEnumerable怎樣才能代替IEnumerator啓用多個消費者一次枚舉集合

+1

你能引用你的參考嗎?在上下文中很難理解這一點。 – usr 2012-07-25 17:54:53

+0

'IEnumerator '是有狀態的,通常代表被迭代的集合中的一個位置。共享一個枚舉器的多個線程需要使用一些同步方法來安全地使用一個。 IEnumerable接口允許每個線程獲得自己的枚舉器,它可以安全地使用而不需要與其他線程協作(除非底層集合發生變化,這是不安全的並且會導致拋出異常)。 – Lee 2012-07-25 18:17:10

回答

4

IEnumerable實施單一方法GetEnumerator(),其返回IEnumerator。由於每次調用該方法時,都會返回一個新的IEnumerator,它有其自己的狀態。這樣,多個線程可以遍歷相同的集合,而不會有一個線程更改另一個線程的當前指針的危險。

如果一個集合實現了IEnumerator,那麼它實際上只能被一次一個線程迭代。請看下面的代碼:

public class EnumeratorList : IEnumerator 
{ 
    private object[] _list = new object[10]; 
    private int _currentIndex = -1; 

    public object Current { get { return _list[_currentIndex] } }; 

    public bool MoveNext() 
    { 
     return ++_currentIndex < 10; 
    } 

    public void Reset() 
    { 
     _currentIndex = -1; 
    } 
} 

鑑於執行,如果兩個線程通過EnumeratorList同時嘗試循環,他們將獲得交錯的結果,而不會看到整個列表。

如果我們將它重構爲IEnumerable,多個線程可以訪問相同的列表,而不會出現這些問題。

public class EnumerableList : IEnumerable 
{ 
    private object[] _list = new object[10]; 

    public IEnumerator GetEnumerator() 
    { 
     return new ListEnumerator(this); 
    } 

    private object this[int i] 
    { 
     return _list[i]; 
    } 

    private class ListEnumerator : IEnumerator 
    { 
     private EnumeratorList _list; 
     private int _currentIndex = -1; 

     public ListEnumerator(EnumeratorList list) 
     { 
      _list = list; 
     } 

     public object Current { get { return _list[_currentIndex] } }; 

     public bool MoveNext() 
     { 
      return ++_currentIndex < 10; 
     } 

     public void Reset() 
     { 
      _currentIndex = -1; 
     } 
    } 
} 

現在,這是一個簡單的,人爲的例子,當然,但我希望這有助於更清楚。

+0

我剛剛開始知道'c#中的任何類型的參數'都是**總是通過值**傳遞......這使得IEnumerator imp class 2將一個對象作爲一個值類型參數,從而允許它有另一個副本。 .. :) – Anirudha 2012-07-25 18:37:34

+1

@Anirudha,我想你是誤導了一些觀點。 C#(.NET)有「指針」,對象通過引用傳遞。該行返回新的ListEnumerator(this);在代碼abose上爲每個枚舉請求創建一個新的實例o Enumerator。這就是它工作的原因。沒有列表的副本。 – devundef 2012-07-25 19:04:41

+0

@devundef我剛剛通過價值和參考parameters.thxx通過skeets文章指出它雖然:):它只是讓我們能夠獨立地內部移動,是的,因爲你說它不是一個副本的名單,但參考...我是馬上.. – Anirudha 2012-07-25 19:20:42

1

MSDN articleIEnumerable提供了正確使用的一個很好的例子。

要直接回答您的問題,正確實施時,用於穿過集合的IEnumerator對象對於每個呼叫者都是唯一的。這意味着您可以讓多個消費者在您的收藏上撥打foreach,並且每個消費者都將擁有自己的枚舉器,並在收藏中擁有自己的索引。

請注意,這僅提供對集合進行修改的基本保護。爲此,您必須使用正確的lock()塊(請參閱here)。

1

考慮代碼:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var test = new EnumTest(); 
     test.ConsumeEnumerable2Times(); 
     Console.ReadKey(); 
    } 
} 

public class EnumTest 
{ 
    public IEnumerable<int> CountTo10() 
    { 
     for (var i = 0; i <= 10; i++) 
      yield return i; 
    } 

    public void ConsumeEnumerable2Times() 
    { 
     var enumerable = CountTo10(); 

     foreach (var n in enumerable) 
     { 
      foreach (int i in enumerable) 
      { 
       Console.WriteLine("Outer: {0}, Inner: {1}", n, i); 
      } 
     } 
    } 
} 

此代碼將產生輸出:

Outer: 0, Inner: 1 
Outer: 0, Inner: 2 
... 
Outer: 1, Inner: 0 
Outer: 1, Inner: 1 
... 
Outer: 10, Inner: 10 

使用IEnumerable的,你可以一遍又一遍枚舉相同的集合。 IEnumerable實際上會爲每個枚舉請求返回一個新的IEnumerator實例。

在上面的示例中EnumTest()方法被調用一次,但返回的IEnumerable被使用了兩次。每次獨立計數到10。

這就是爲什麼「多個消費者可以一次枚舉集合而不會相互干擾」。您可以將相同的IEnumerable對象傳遞給2個方法,並且它們將獨立枚舉集合。用IEnumerator你不能實現這一點。

對不起,我的英語。

1

實現IEnumerator的類型必須具有可以迭代它的方法和屬性,即MoveNext()Reset()Current。如果您有多個線程同時嘗試遍歷此對象,會發生什麼情況?他們會互相接近,因爲他們都調用相同的MoveNext()函數,這會修改相同的Current屬性。

實現IEnumerable的類型必須能夠提供IEnumerator的實例。現在當多個線程迭代對象時會發生什麼?每個線程都有一個IEnumerator對象的獨立實例。返回的IEnumerator與您的集合不是同一類型的對象。它們完全不同。但是,他們知道如何獲取下一個項目並顯示集合中的當前項目,並且每個對象都將擁有關於枚舉當前狀態的內部數據。因此,他們不會互相踩踏並從不同的線程安全地迭代您的集合。

有時候,集合類型會自己實現IEnumerator(它是它自己的枚舉器),然後通過返回自己來實現IEnumerable。在這種情況下,對於多線程,您將不會獲得任何回報,因爲它們仍然使用相同的對象進行枚舉。這是倒退。相反,正確的過程是首先爲你的集合實現一個單獨的(可嵌套的)枚舉器類型。然後通過返回該類型的新實例並通過保留私有實例來實現IEnumerator來實現IEnumerable。