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

大浩大大_1921

大浩大大_1921

2026-01-19

605人浏览

原创

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<int> Numbers()
{
    for (int i = 0; i // ✅ 安全:每个线程拿到新枚举器
var sharedSource = Numbers(); // IEnumerable<int>
Task.Run(() => { foreach (var x in sharedSource) Console.WriteLine(x); });
Task.Run(() => { foreach (var x in sharedSource) Console.WriteLine(x); });<p>// ❌ 危险:共享同一枚举器实例
var enumerator = Numbers().GetEnumerator();
Task.Run(() => { while (enumerator.MoveNext()) Console.WriteLine(enumerator.Current); });
Task.Run(() => { while (enumerator.MoveNext()) Console.WriteLine(enumerator.Current); }); // 可能抛 InvalidOperationException</p></int></int>

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

C#
C#

"避开 C# 常见错误——空陷阱、异步陷阱、LINQ 陷阱及资源泄漏。"

下载

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

7559

3

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.08.29

3585

6

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

2025.08.29

4728

10

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

2025.08.29

3145

10

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

2025.10.23

854

12

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4878

6

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

2025.12.24

1369

20

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2026.01.21

463

30

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

2026.01.21

599

24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程