用IEnumerable / yield解释这个奇怪的行为
本文关键字:IEnumerable yield 解释 | 更新日期: 2023-09-27 18:15:40
猜一猜…当I == 0时,这个程序需要多长时间才能产生第一个输出?应该是即时的,对吧?通过对yield
的惰性评估它应该在那之后快速连续地产生输出,对吧?
static void Main(string[] args)
{
Stopwatch stopwatch = Stopwatch.StartNew();
int i = 0;
foreach (var item in massiveYieldStatement())
{
if (i++ % 10000 == 0)
Console.WriteLine(stopwatch.ElapsedMilliseconds / 1000);
}
Console.ReadKey();
}
static IEnumerable<string> massiveYieldStatement()
{
yield return "a";
yield return "a";
.. repeat 200,000 times !!
yield return "a";
}
但它没有!它会在4到21分钟之间没有输出,然后很快完成——在一个案例中不到60毫秒!在这几分钟内,一个核心的CPU价值的100%被使用,内存使用也在增长。在我遇到这种情况的实际场景中,甚至在第一次迭代发生之前就抛出了Stackoverflow
异常!我在Visual Studio的调试模式和命令提示符的发布模式下尝试过。我已经在Windows 7 x64和Windows Server 2008 R2 x64上试过了。
注意:这不是真正的代码:真正的代码有更少的yield语句,但更复杂。
此代码生成的程序集大小为几MB。yield return
是一个特殊的野兽,因为它看起来看似简单,但c#编译器实际上生成一个类("状态机")来实现massiveYieldStatement方法。我很确定,您正在等待JIT编译器编译该类的MoveNext()方法(您可以用ildasm验证:如果您尝试打开MoveNext()方法,它也需要很多时间)。
问题不在于收益率,而在于返回200K收益率的函数(顺便说一下)。10万行已经拖慢了我的VS)。每次在从IEnumerable<string>.GetEnumerator
返回的IEnumerator
上执行第一个MoveNext()
时,都需要对其进行评估并生成一个状态类new。
static IEnumerable<string> massiveYieldStatement()
{
for(int i = 0; i < 200000; ++i)
yield return "a";
}
运行速度如预期的那样快,因为求值很快。