6

当可能在 finally 块中抛出异常时,如何传播这两个异常 - 从 catch 和从 finally?

作为一种可能的解决方案 - 使用 AggregateException:

internal class MyClass
{
    public void Do()
    {
        Exception exception = null;
        try
        {
            //example of an error occured in main logic
            throw new InvalidOperationException();
        }
        catch (Exception e)
        {
            exception = e;
            throw;
        }
        finally
        {
            try
            {
                //example of an error occured in finally
                throw new AccessViolationException();
            }
            catch (Exception e)
            {
                if (exception != null)
                    throw new AggregateException(exception, e);
                throw;
            }
        }
    }
}

这些异常可以像下面的代码片段一样处理:

private static void Main(string[] args)
{
    try
    {
        new MyClass().Do();
    }
    catch (AggregateException e)
    {
        foreach (var innerException in e.InnerExceptions)
            Console.Out.WriteLine("---- Error: {0}", innerException);
    }
    catch (Exception e)
    {
        Console.Out.WriteLine("---- Error: {0}", e);
    }

    Console.ReadKey();
}
4

2 回答 2

3

我经常遇到同样的情况,但还没有找到更好的解决方案。但我认为 OP 建议的解决方案是合格的。

这是对原始示例的轻微修改:

internal class MyClass
{
    public void Do()
    {
        bool success = false;
        Exception exception = null;
        try
        {
            //calling a service that can throw an exception
            service.Call();
            success = true;
        }
        catch (Exception e)
        {
            exception = e;
            throw;
        }
        finally
        {
            try
            {
                //reporting the result to another service that also can throw an exception
                reportingService.Call(success);
            }
            catch (Exception e)
            {
                if (exception != null)
                    throw new AggregateException(exception, e);
                throw;
            }
        }
    }
}

恕我直言,在这里忽略一个或另一个异常将是致命的。

另一个例子:想象一个校准设备 (DUT) 的测试系统,因此必须控制另一个向 DUT 发送信号的设备。

internal class MyClass
{
    public void Do()
    {
        Exception exception = null;
        try
        {
            //perform a measurement on the DUT
            signalSource.SetOutput(on);
            DUT.RunMeasurement();
        }
        catch (Exception e)
        {
            exception = e;
            throw;
        }
        finally
        {
            try
            {
                //both devices have to be set to a valid state at end of the procedure, independent of if any exception occurred
                signalSource.SetOutput(off);
                DUT.Reset();
            }
            catch (Exception e)
            {
                if (exception != null)
                    throw new AggregateException(exception, e);
                throw;
            }
        }
    }
}

在此示例中,重要的是在该过程之后将所有设备设置为有效状态。但是这两个设备也可以在 finally 块中抛出异常,这些异常不能丢失或忽略。

关于调用者的复杂性,我也没有看到任何问题。例如,当使用 System.Threading.Tasks 时,WaitAll() 方法也可以抛出必须以相同方式处理的 AgregateExceptions。

关于@damien 评论的另一个注意事项:仅捕获异常以将其包装到 AggregateException 中,以防 finally 块抛出。没有对异常做任何其他事情,也没有以任何方式处理它。

对于那些想要这样做的人,您可以使用我最近创建的一个小助手类:

public static class SafeExecute
{
    public static void Invoke(Action tryBlock, Action finallyBlock, Action onSuccess = null, Action<Exception> onError = null)
    {
        Exception tryBlockException = null;

        try
        {
            tryBlock?.Invoke();
        }
        catch (Exception ex)
        {
            tryBlockException = ex;
            throw;
        }
        finally
        {
            try
            {
                finallyBlock?.Invoke();
                onSuccess?.Invoke();
            }
            catch (Exception finallyBlockException)
            {
                onError?.Invoke(finallyBlockException);

                // don't override the original exception! Thus throwing a new AggregateException containing both exceptions.
                if (tryBlockException != null)
                    throw new AggregateException(tryBlockException, finallyBlockException);

                // otherwise re-throw the exception from the finally block.
                throw;
            }
        }
    }
}

并像这样使用它:

public void ExecuteMeasurement(CancellationToken cancelToken)
{
    SafeExecute.Invoke(
        () => DUT.ExecuteMeasurement(cancelToken),
        () =>
        {
            Logger.Write(TraceEventType.Verbose, "Save measurement results to database...");
            _Db.SaveChanges();
        },
        () => TraceLog.Write(TraceEventType.Verbose, "Done"));
}
于 2018-08-24T07:36:32.790 回答
2

正如评论所暗示的那样,这可能表明“不幸”的结构化代码。例如,如果您发现自己经常处于这种情况,这可能表明您在方法中尝试做的太多。如果您无能为力,您只想抛出异常(您的代码“陷入”无法编程的问题。如果有合理的期望,您只想捕获异常,您可以做一些有用的事情. 框架中有一个 OutOfMemoryException,但你很少会看到有人试图抓住它,因为在大多数情况下,这意味着你已经筋疲力尽 :-)

如果 finally 块中的异常是 try 块中异常的直接结果,则返回该异常只会使真正的问题复杂化或模糊,使其更难解决。在极少数情况下,如果有返回异常的验证原因,那么使用 AggregateException 将是执行此操作的方法。但在采用这种方法之前,问问自己是否可以将异常分成单独的方法,在这些方法中可以(单独)返回和处理单个异常。

于 2013-10-20T16:48:59.530 回答