简体   繁体   English

如何在C#中的yield return方法中正确抛出Exception

[英]How to properly throw an Exception inside yield return method in C#

See edits below for reproducing the behavior that I describe in this problem. 请参阅下面的编辑,以重现我在此问题中描述的行为。

The following program will never end, because the yield return construct in C# calls the GetStrings() method indefinitely when an exception is thrown. 以下程序永远不会结束,因为C#中的yield return构造会在抛出异常时无限期地调用GetStrings()方法。

class Program
{
    static void Main(string[] args)
    {
        // I expect the Exception to be thrown here, but it's not
        foreach (var str in GetStrings())
        {
            Console.WriteLine(str);
        }
    }

    private static IEnumerable<string> GetStrings()
    {
        // REPEATEDLY throws this exception
        throw new Exception();
        yield break;
    }
}

For this trivial example, I could obviously use return Enumerable.Empty<string>(); 对于这个简单的例子,我显然可以使用return Enumerable.Empty<string>(); instead, and the problem goes away. 相反,问题就消失了。 However in a more interesting example, I'd expect the exception to be thrown once, then have the method stop being called and throw the exception in the method that's "consuming" the IEnumerable . 但是在一个更有趣的例子中,我希望抛出异常一次,然后调用方法停止并在“消耗” IEnumerable的方法中抛出异常。

Is there a way to produce this behavior? 有没有办法产生这种行为?

EDIT: ok, the problem is different than I first thought. 编辑:好的,问题与我的想法不同。 The program above does NOT end, and the foreach loop behaves like an infinite loop. 上面的程序没有结束, foreach循环表现得像一个无限循环。 The program below DOES end, and the exception is displayed on the console. 以下程序将结束,并在控制台上显示异常。

class Program
{
    static void Main(string[] args)
    {
        try
        {
            foreach (var str in GetStrings())
            {
                Console.WriteLine(str);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }

    private static IEnumerable<string> GetStrings()
    {
        throw new Exception();
        yield break;
    }
}

Why does the try ... catch block make a difference in this case? 为什么try ... catch块在这种情况下会有所不同? This seems very strange to me. 这对我来说似乎很奇怪。 Thanks to @AndrewKilburn for his answer already for pointing me to this. 感谢@AndrewKilburn的回答已经指向我。

EDIT #2: From a Command Prompt, the program executes the same in both cases. 编辑#2:从命令提示符开始,程序在两种情况下都执行相同的操作。 In Visual Studio Enterprise 2015, Update 2, whether I compile in Debug or Release, the behavior above is what I am seeing. 在Visual Studio Enterprise 2015 Update 2中,无论是在Debug还是Release中编译,上面的行为就是我所看到的。 With the try ... catch , the program ends with an exception, and without it Visual Studio never closes the program. 使用try ... catch ,程序以异常结束,没有它,Visual Studio永远不会关闭程序。

EDIT #3: Fixed For me, the issue was resolved by the answer by @MartinBrown. 编辑#3:固定对我来说,问题是由@MartinBrown的答案解决的。 When I uncheck Visual Studio's option under Debug > Options > Debugging > General > "Unwind the call stack on unhandled exceptions" this problem goes away. 当我在“调试”>“选项”>“调试”>“常规”>“解除对未处理的异常的调用堆栈”下取消选中Visual Studio的选项时,此问题就会消失。 When I check the box again, then the problem comes back. 当我再次检查该框时,问题又回来了。

The behaviour being seen here is not a fault in the code; 这里看到的行为不是代码中的错误; rather it is a side effect of the Visual Studio debugger. 相反,它是Visual Studio调试器的副作用。 This can be resolved by turning off stack unwinding in Visual Studio. 这可以通过在Visual Studio中关闭堆栈展开来解决。 Try going into Visual Studio options Debugging/General and unchecking "Unwind the call stack on unhandled exceptions". 尝试进入Visual Studio选项Debugging / General并取消选中“在未处理的异常上展开调用堆栈”。 Then run the code again. 然后再次运行代码。

What happens is that when your code hits a completely unhandled exception Visual Studio is unwinding the call stack to just before the line in your code that caused the exception. 当您的代码遇到完全未处理的异常时,Visual Studio会将调用堆栈展开到代码中导致异常的行之前。 It does this so that you can edit the code and continue execution with the edited code. 它这样做是为了您可以编辑代码并继续执行编辑的代码。

The issue seen here looks like an infinite loop because when you re-start execution in the debugger the next line to run is the one that just caused an exception. 这里看到的问题看起来像一个无限循环,因为当您在调试器中重新开始执行时,下一行要运行的是刚引起异常的那一行。 Outside the debugger the call stack would be completely unwound on an unhandled exception and thus would not cause the same loop that you get in the debugger. 在调试器之外,调用堆栈将在未处理的异常上完全展开,因此不会导致您在调试器中获得相同的循环。

This stack unwinding feature can be turned off in the settings, it is enabled by default. 可以在设置中关闭此堆栈展开功能,默认情况下启用它。 Turning it off however will stop you being able to edit code and continue without first unwinding the stack yourself. 然而,将其关闭将阻止您编辑代码并继续,而无需先自行展开堆栈。 This however is quite easy to do either from the call stack window or by simply selecting 'Enable Editing' from the Exception Assistant. 但是,从调用堆栈窗口或从Exception Assistant中选择“启用编辑”,这很容易。

The following program will never end 以下程序永远不会结束

That's false. 那是假的。 The program is going to end quite quickly. 该计划将很快结束。

because the yield return construct in C# calls the GetStrings() method indefinitely when an exception is thrown. 因为C#中的yield return构造在抛出异常时无限期地调用GetStrings()方法。

This is false. 这是错误的。 It doesn't do this at all. 它根本不会这样做。

I'd expect the exception to be thrown once, then have the method stop being called and throw the exception in the method that's "consuming" the IEnumerable . 我希望抛出异常一次,然后调用方法停止并在“消耗” IEnumerable的方法中抛出异常。

That's exactly what does happen. 这正是确实会发生。

Is there a way to produce this behavior? 有没有办法产生这种行为?

Use the code you already provided. 使用您已提供的代码。

  class Program {
        static void Main(string[] args) {
            try {
                foreach (var item in GetStrings()) {
                    Console.WriteLine();
                }
            }
            catch (Exception ex) {

        }
    }
    private static IEnumerable<string> GetStrings() {
        // REPEATEDLY throws this exception
        throw new Exception();
        yield break;
    }
}

Putting it in a try catch causes it to break out and do whatever you want 将它放入试试捕获会导致它爆发并做任何你想做的事情

class Program
{
    public static int EnumerableCount;

    static void Main(string[] args)
    {
        EnumerableCount = 0;
        try
        {
            foreach (var str in GetStrings())
            {
                Console.WriteLine(str);
                Console.Read();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            Console.Read();
        }
    }

    private static IEnumerable<string> GetStrings()
    {
        EnumerableCount++;
        var errorMessage = string.Format("EnumerableCount: {0}", EnumerableCount);
        throw new Exception(errorMessage);
        yield break;
    }
}

has the following output: 有以下输出:

System.Exception: EnumerableCount: 1
  at {insert stack trace here}

The execution flow goes into the GetStrings() method the for the first iteration, the exception is thrown and caught in the Main() method. 执行流进入第一次迭代的GetStrings()方法,抛出异常并捕获Main()方法。 After hitting enter, the program exits. 点击进入后,程序退出。

Removing the try catch in the Main() method causes the exception to go unhandled. 删除Main()方法中的try catch会导致异常无法处理。 The output is then: 输出是:

Unhandled Exception: System.Exception: EnumerableCount: 1
  at {insert stack trace here}

and the program crashes. 程序崩溃了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM