【问题标题】:Locking inside GetEnumerator()... what happens in a foreach with LINQ extensions?在 GetEnumerator() 内部锁定...在带有 LINQ 扩展的 foreach 中会发生什么?
【发布时间】:2018-09-29 03:29:43
【问题描述】:

给定示例类:

internal Stuff<T> : IEnumerable<T> where T : Foo
{
    private readonly object Sync = new object();
    private readonly T[] TObjects;

    public IEnumerator<T> GetEnumerator()
    {
        lock(Sync)
            using (IEnumerator<T> safeEnum = TObjects.AsEnumerable().GetEnumerator())
                while (safeEnum.MoveNext())
                    yield return safeEnum.Current;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    //other things
}

如果我在 foreach 循环中迭代这个类,整个 foreach 循环就会被对象的锁定方案锁定。 我的问题是,如果我在 linq 查询中使用它会发生什么,例如:

Stuff stuff = new Stuff() { /*some foos*/ };
var things = stuff.Where(/*some predicate*/);

foreach(Foo foo in things)
{
    //am i locked?
}

foreach(Foo foo in stuff.Where(/*some predicate*/))
{
    //am i locked?
}

所以归结为,查询链接在后台是如何工作的?

【问题讨论】:

  • 我被屏蔽了吗?其实不是。如果您有对该对象的引用,并且没有其他代码拥有它,那么您将永远不会被阻止。如果你迭代它(在一个线程上说 T1)并且其他一些代码可以访问完全相同的对象并在另一个线程上迭代它,它将被阻塞,直到 T1 完成迭代。这里重要的部分是对象被锁定而不是类。
  • @CodingYoshi 已锁定,未阻止。锁定机制传播到 foreach 循环中,我问它是否也通过对对象进行的 LINQ 查询传播到 foreach 循环中。
  • LINQ 是基于它的抽象,所以是的,同样的规则也适用。
  • 对于您发布的代码,您不需要锁,因为多个线程可以只读访问数据结构。如果您进行一些修改,那么确保线程安全很重要,但我宁愿为该用例使用并发收集
  • Internally Linq Where 只是根据谓词减少集合,你的两个代码sn-ps没有区别,当多个线程访问数据结构时,锁会起作用,但不需要只读枚举器

标签: c# linq locking ienumerable


【解决方案1】:

让我们详细检查您的代码,除了stuff&lt;T&gt; 应该是一个类的小错字之外,您对IEnumerator&lt;T&gt; 的实现如下:

public IEnumerator<T> GetEnumerator()
{
    lock (Sync)
     using (IEnumerator<T> safeEnum = TObjects.AsEnumerable().GetEnumerator())
        while (safeEnum.MoveNext())
          yield return safeEnum.Current;
}

这是Enumeration 的实现,而不仅仅是Enumerator,因为在获取枚举器后,您通过进一步移动枚举器来进一步处理集合,理想情况下您的自定义实现应该是这样的:

IEnumerator IEnumerable.GetEnumerator() =&gt; TObjects.AsEnumerable().GetEnumerator(),因为枚举是由foreach 循环完成的,它通过访问已实现的枚举器并调用MoveNext 方法和Current 属性来完成,因此当前的实现可以更加简洁


枚举器实现

TObjects.AsEnumerable().GetEnumerator()实现如下,它是访问静态类System.Linq.EnumerableGetEnumerator()实现,内部有抽象类Iterator&lt;T&gt;的实现。

  1. AsEnumerable()是一个简单的扩展方法

     public static IEnumerable<TSource> AsEnumerable<TSource>(this IEnumerable<TSource> 
     source)
     {
        return source;
     }
    
  2. 现在由于我们的源是一个数组T[],我们正在应用一个Where 子句,因此在下面的 Enumerable 实现中指定

     public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
        if (source == null) throw Error.ArgumentNull("source");
        if (predicate == null) throw Error.ArgumentNull("predicate");
        if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate);
        if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate);
        if (source is List<TSource>) return new WhereListIterator<TSource>((List<TSource>)source, predicate);
        return new WhereEnumerableIterator<TSource>(source, predicate);
    }
    

我们得到WhereArrayIterator,其对Enumeration / Iteration的实现如下:

    class WhereArrayIterator<TSource> : Iterator<TSource>
    {
        TSource[] source;
        Func<TSource, bool> predicate;
        int index;

        public WhereArrayIterator(TSource[] source, Func<TSource, bool> predicate) {
            this.source = source;
            this.predicate = predicate;
        }

        public override Iterator<TSource> Clone() {
            return new WhereArrayIterator<TSource>(source, predicate);
        }

        public override bool MoveNext() {
            if (state == 1) {
                while (index < source.Length) {
                    TSource item = source[index];
                    index++;
                    if (predicate(item)) {
                        current = item;
                        return true;
                    }
                }
                Dispose();
            }
            return false;
        }

        public override IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) {
            return new WhereSelectArrayIterator<TSource, TResult>(source, predicate, selector);
        }

        public override IEnumerable<TSource> Where(Func<TSource, bool> predicate) {
            return new WhereArrayIterator<TSource>(source, CombinePredicates(this.predicate, predicate));
        }
    }

如 cmets 中所述,将枚举器获取到集合是线程安全的,因为它是只读的。所有提到的代码和类相关细节都可以在MS Reference Source上找到,它有API搜索浏览器

【讨论】:

  • 再一次,这门课上的内容比我向您展示的要多得多。我不是在问这个集合是否是线程安全的,或者是否有任何东西阻塞了其他任何东西。我只想知道锁是否会通过 where 扩展传播到 foreach 循环中...如果我在您的帖子中正确遵循了逻辑,您会强调它会,谢谢。
  • 是的,因为锁是关于线程的,虽然它允许一个线程,但会阻塞同一关键区域中的任何其他线程,这里包括 linq 在内的所有代码都将在同一个线程上,因此通过.
猜你喜欢
  • 2013-01-27
  • 1970-01-01
  • 1970-01-01
  • 2011-08-07
  • 1970-01-01
  • 1970-01-01
  • 2019-03-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多