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



给定示例类:

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

所以它归结为,查询链接如何在引擎盖下工作?

让我们详细回顾一下你的代码,除了stuff<T>应该是一个类的小错别字之外,你对IEnumerator<T>的实现如下:

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() => TObjects.AsEnumerable().GetEnumerator(),由于枚举是由foreach循环完成的,它通过访问实现的枚举器并调用MoveNext方法和Current属性来完成枚举,因此当前的实现可以更简洁


枚举器实现

TObjects.AsEnumerable().GetEnumerator()实现如下,它是访问静态类System.Linq.EnumerableGetEnumerator()实现,该实现内部具有抽象类Iterator<T>的实现。

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

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

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

如注释中所述,将枚举器获取到集合是线程安全的,因为它是只读的。所有提到的代码和类相关详细信息都可以在MS参考源上找到,该参考源具有API搜索浏览器

最新更新