c# yield return 生成的迭代器是线程安全的吗

yield return 迭代器不是线程安全的,因其生成的状态机类字段(如1__state和捕获变量)被多个线程并发读写而无同步机制,导致InvalidOperationException、元素跳过或重复等未定义行为。

c# yield return 生成的迭代器是线程安全的吗

不是线程安全的。 C# 中用 yield return 生成的迭代器(即实现了 IEnumerable 的方法)本身不提供任何线程同步机制,多个线程同时调用 GetEnumerator() 并遍历,或对同一个枚举器实例并发调用 MoveNext()/Current,会导致未定义行为,常见表现为 InvalidOperationException、跳过元素、重复返回、甚至死锁(取决于底层状态机实现)。

为什么 yield return 迭代器不是线程安全的

每个 yield return 方法在编译后会生成一个隐藏的状态机类(如 d__0),该类包含:

  • 一个整型字段 1__state,记录当前执行位置(-2=已结束,-1=未开始,0+为具体 yield 点)
  • 所有被闭包捕获的局部变量作为字段存储
  • Current 属性和 MoveNext() 方法直接读写这些字段,无锁、无 volatile、无内存屏障

这意味着:只要两个线程操作的是同一个枚举器实例(IEnumerator),就会竞争修改同一组字段。

哪些场景下容易出问题

以下情况极易触发线程安全问题:

  • 多个线程共用同一个 IEnumerator 实例(例如把 GetEnumerator() 结果存为字段后多线程调用 MoveNext()
  • 在异步方法中(如 async Task>)返回 yield return 序列,但外部未 await 就直接枚举 —— 此时状态机可能跨线程切换,而枚举器本身仍无保护
  • yield return 方法的结果(IEnumerable)传给并行 LINQ(如 AsParallel().Select(...)),后者可能在多个线程中调用 GetEnumerator() 并并发消费

如何安全地在多线程中使用 yield return 序列

核心原则是:**确保每个线程拥有独立的枚举器实例,并避免共享状态。** 常见做法包括:

  • 每次需要遍历时,都重新调用原始 IEnumerable 方法(即重新创建状态机实例),而不是复用枚举器
  • 若需缓存结果供多线程读取,先调用 .ToList().ToArray() 落实为线程安全的不可变集合(注意:这会失去延迟执行优势)
  • 如必须保持延迟执行且支持并发访问,可手动包装一层线程安全的枚举逻辑(例如用 lock 同步 MoveNext()Current,但会严重损害性能,且违背 yield return 的设计初衷)
public static IEnumerable Numbers()
{
    for (int i = 0; i < 10; i++)
    {
        yield return i;
    }
}

// ✅ 安全:每个线程拿到新枚举器 var sharedSource = Numbers(); // IEnumerable Task.Run(() => { foreach (var x in sharedSource) Console.WriteLine(x); }); Task.Run(() => { foreach (var x in sharedSource) Console.WriteLine(x); });

// ❌ 危险:共享同一枚举器实例 var enumerator = Numbers().GetEnumerator(); Task.Run(() => { while (enumerator.MoveNext()) Console.WriteLine(enumerator.Current); }); Task.Run(() => { while (enumerator.MoveNext()) Console.WriteLine(enumerator.Current); }); // 可能抛 InvalidOperationException

真正需要并发消费的延迟序列,应考虑用 ChannelIObservable 替代,它们从设计上就支持多订阅者与线程安全推送。

造次

造次

Liblib打造的AI原创IP视频创作社区

下载

https://www.php.cn/faq/2000524.html

发表回复

Your email address will not be published. Required fields are marked *