锁定在 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.Enumerable
的GetEnumerator()
实现,内部有抽象[=57]的实现=] Iterator<T>
.
AsEnumerable()
是简单的扩展方法
public static IEnumerable<TSource> AsEnumerable<TSource>(this IEnumerable<TSource>
source)
{
return source;
}
现在因为我们的源是一个数组 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 搜索浏览器
给定样本 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.Enumerable
的GetEnumerator()
实现,内部有抽象[=57]的实现=] Iterator<T>
.
AsEnumerable()
是简单的扩展方法public static IEnumerable<TSource> AsEnumerable<TSource>(this IEnumerable<TSource> source) { return source; }
现在因为我们的源是一个数组
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 搜索浏览器