锁定在 GetEnumerator() 中...在带有 LINQ 扩展的 foreach 中会发生什么?

Locking inside GetEnumerator()... what happens in a foreach with LINQ extensions?

给定样本 class:

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 循环中重复此 class,整个 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?
}

归根结底,查询链接是如何工作的?

让我们详细检查您的代码,除了 stuff<T> 应为 class 的小错别字外,您对 IEnumerator<T> 的实现如下:

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

这是 Enumeration 的实现,而不仅仅是 Enumerator,因为 post 获得了枚举器,您正在通过进一步移动枚举器来进一步处理集合,最好是您的自定义实现应该是这样的:

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


枚举器实现

TObjects.AsEnumerable().GetEnumerator()实现如下,是访问静态classSystem.Linq.EnumerableGetEnumerator()实现,内部有抽象[=57]的实现=] Iterator<T>.

  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,其枚举/迭代的实现如下:

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

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