用IEnumerable / yield解释这个奇怪的行为

时间:2011-11-24 08:05:21

标签: c# stack-overflow jit yield

猜一下......当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分钟,然后快速完成 - 在一个案例中60ms以下!在这几分钟内,使用了一个核心CPU的100%并且内存使用量增加。在我遇到这种情况的实际场景中,在第一次迭代发生之前抛出了Stackoverflow异常!我在Visual Studio中以调试模式尝试了它,并在命令提示符下以发布模式尝试了它。我在Windows 7 x64和Windows Server 2008 R2 x64上尝试过它。

有人能解释这里发生了什么吗?它会为你重复吗?

注意:这不是真正的代码:真正的代码具有更少的yield语句,但要复杂得多。这只是最简单的复制品。

2 个答案:

答案 0 :(得分:4)

此代码生成的程序集大几MB。 yield return是一个特殊的野兽,因为它看起来很简单,但C#编译器实际上生成了一个类('state machine')来实现massiveYieldStatement方法。我很确定你正在等待JIT编译器编译该类的MoveNext()方法(你可以用ildasm验证它:如果你试图打开MoveNext()方法,它也需要花费很多时间)。

答案 1 :(得分:2)

问题不在于产量,而是返回200K产量的函数(顺便说一下,100K线已经减慢了我的VS)。每当您从MoveNext()返回的IEnumerator上执行第一次IEnumerable<string>.GetEnumerator时,需要对其进行评估并生成新的州级。

static IEnumerable<string> massiveYieldStatement()
{
    for(int i = 0; i < 200000; ++i)
        yield return "a";
}
由于评估很快,

按预期运行得很快。