【问题标题】:does using IEnumerable interface reduce concurrency issues使用 IEnumerable 接口会减少并发问题吗
【发布时间】:2012-07-25 17:42:27
【问题描述】:

根据Albahari brothers[Page No.273],使用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 启用多个消费者一次枚举集合

【问题讨论】:

  • 你能引用你的参考吗?脱离上下文很难理解这一点。
  • IEnumerator<T> 是有状态的,通常表示正在迭代的集合中的位置。共享一个枚举器的多个线程需要使用某种同步方法才能安全地使用一个。 IEnumerable<T> 接口允许每个线程获取自己的枚举器,它可以安全地使用它而无需与其他线程合作(除非底层集合发生更改,这不安全并且会导致抛出异常)。跨度>

标签: c# .net collections concurrency ienumerable


【解决方案1】:

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;
        }
    }
}

当然,这是一个简单的、人为的示例,但我希望这有助于使其更加清晰。

【讨论】:

  • 我刚刚知道 c# 中的any type of parameter 总是按值传递...这使得 IEnumerator imp 类 2 将对象作为值类型参数并因此允许它有另一个副本...:)
  • @Anirudha,我认为你误解了一些观点。 C# (.NET) 有“指针”并且对象是通过引用传递的。该行 return new ListEnumerator(this);上面的代码是为每个枚举请求创建一个新的实例o Enumerator。这就是它起作用的原因。没有列表副本。
  • @devundef 我只是在浏览关于 value&ref parameters.thxx 的 skeets 文章以指出这一点。:) 它只是使我们能够在内部独立移动,是的,正如你所说,它不是列表的副本,而是一个参考......我现在是......
【解决方案2】:

IEnumerableMSDN article 提供了正确使用的一个很好的例子。

为了直接回答您的问题,正确实施后,用于遍历集合的 IEnumerator 对象对于每个调用者都是唯一的。这意味着您可以让多个消费者在您的集合上调用 foreach,并且每个消费者都有自己的枚举器,并在集合中拥有自己的索引。

请注意,这仅提供对集合进行修改的基本保护。为此,您必须使用正确的lock() 块(请参阅here)。

【讨论】:

    【解决方案3】:

    考虑代码:

        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 被使用了 2 次。每次独立计数到 10。

    这就是“多个消费者可以一次枚举集合而不会相互干扰”的原因。您可以将相同的 IEnumerable 对象传递给 2 个方法,它们将独立枚举集合。使用 IEnumerator 你无法做到这一点。

    对不起我的英语。

    【讨论】:

      【解决方案4】:

      实现IEnumerator 的类型必须具有可以对其进行迭代的方法和属性,即MoveNext()Reset()Current。如果你有多个线程同时尝试迭代这个对象会发生什么?它们会互相踩踏,因为它们都调用了相同的 MoveNext() 函数,该函数修改了相同的 Current 属性。

      实现IEnumerable 的类型必须能够提供IEnumerator 的实例。现在当多个线程迭代对象时会发生什么?每个线程都有一个单独的 IEnumerator 对象实例。返回的 IEnumerator 与您的集合的对象类型不同。它们是完全不同的东西。但是,他们确实知道如何获取下一个项目并显示集合的当前项目,并且每个对象都将拥有关于当前枚举状态的内部数据。因此,它们不会互相踩踏,而是从不同的线程安全地迭代您的集合。

      有时,集合类型会为自己实现 IEnumerator(它是它自己的枚举器),然后通过返回自身来实现 IEnumerable。在这种情况下,对于多个线程,您将一无所获,因为它们仍然使用相同的对象进行枚举。这是倒退。相反,正确的过程是首先为您的集合实现一个单独的(可以嵌套的)枚举器类型。然后,您通过返回该类型的新实例来实现 IEnumerable,并通过保留私有实例来实现 IEnumerator。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多