为什么异常会从事件处理程序中传播出去?

时间:2022-06-27 00:01:04

Consider the following program. How is the behaviour that it displays (namely that exceptions will propagate out of an event handler) a "good thing"? As far as I can tell, it could only ever be bad; unexpected exceptions popping up from functions that they shouldn't. In my particular case, it was killing threads. So, is this behaviour actually a good thing in some cases? Is this a reason to say that letting exceptions out of event-handlers is poor design?

考虑以下程序。它显示的行为(即异常将从事件处理程序传播出来)是一个“好事”?据我所知,它只会是坏事;意外异常从他们不应该的功能弹出。在我的特殊情况下,它是杀死线程。那么,在某些情况下,这种行为实际上是件好事吗?这是否有理由说从事件处理程序中排除异常是糟糕的设计?

static class Program {
    static void Main()
    {
        Foo foo = new Foo();
        foo.SomeEvent += ThrowException;

        try 
        {
            foo.OnSomeEvent();
        } 
        catch (Exception) 
        {
            // This is printed out
            Console.WriteLine("Exception caught!");
        }
    }

    static void ThrowException(object sender, EventArgs e)
    {
        throw new Exception();
    }
}

// Define other methods and classes here
class Foo 
{
    public event EventHandler SomeEvent;

    public void OnSomeEvent()
    {
        SomeEvent(this, EventArgs.Empty);
    }
}

6 个解决方案

#1


What's your preferred alternative - silently swallowing the exception? I wouldn't like that at all.

什么是你的首选替代方案 - 默默地吞下这个例外?我完全不喜欢这样。

Events are just a way of implementing the observer pattern, really. If a listener throws an exception, I'd absolutely expect that exception to be thrown back to the caller. Any other behaviour I can think of would effectively treat the exception as unimportant. The whole point of exceptions is that when something goes wrong, you find out about it quickly and implicitly. You have to explicitly handle exceptions, so that you don't go on your merry way in a corrupt state without realising it.

实际上,事件只是实现观察者模式的一种方式。如果一个监听器抛出一个异常,我绝对希望将该异常抛回给调用者。我能想到的任何其他行为都会有效地将异常视为不重要。异常的全部意义在于,当出现问题时,您会快速而隐含地发现它。你必须明确地处理异常,这样你就不会在没有意识到的情况下以腐败的状态继续你的快乐方式。

You make a valid point about whose responsibility it is to handle exceptions. Broadly speaking, I find it best to assume that just about anything can throw an exception at any time. Other than special cases where I know a specific exception may occur and I can handle it, I don't usually catch exceptions other than at the top level - except possibly to wrap and rethrow, or log and rethrow.

您提出了一个有效的观点,即处理异常的责任。从广义上讲,我发现最好假设任何事情都可以随时抛出异常。除了我知道可能发生特定异常并且我可以处理它的特殊情况之外,我通常不会捕获除顶层以外的异常 - 除了可能包装和重新抛出,或记录和重新抛出。

Now it's possible that one of your event handlers really shouldn't be throwing an exception - that they haven't really run into an error condition - but what should happen if it's a perfectly reasonable exception which indicates a serious problem? While a program crashing is ugly, it's often better than continuing with some of it malfunctioning, possibly corrupting persisted state etc.

现在你的一个事件处理程序可能真的不应该抛出一个异常 - 它们并没有真正遇到错误状态 - 但是如果它是一个完全合理的异常表明存在严重问题会发生什么呢?虽然程序崩溃是丑陋的,但通常比继续其中一些故障,可能破坏持久状态等更好。

Fundamentally, I don't think the CS/SE field has got error handling "right" yet. I'm not even sure that there is an elegant way of doing the right thing which is simple to express in all situations... but I hope that the current situation isn't as good as it gets.

从根本上说,我认为CS / SE领域还没有“正确”的错误处理。我甚至不确定是否有一种优雅的方式来做正确的事情,这种方式在所有情况下都很容易表达......但我希望目前的情况并不像它那样好。

#2


The main aspect of exception handling discussed here is: do not catch exception if you don't know how to handle it. But let's talk about the observer pattern, where notifier emits event (or signal) about it's state change and listeners handle it. A good example of a notifier is a button emitting 'clicked' event. Does a button care about who are the listeners and what they do? Not really. If you're a listener and you got this event, then you've got a job to do. And if you can't do it, you have to handle this error or inform user, because passing exception to the button makes no sense - button definitely does not know how to handle errors of listener job. And buttons state changer (probably some message loop) does not either - your application will end up at Main() with a crash.

这里讨论的异常处理的主要方面是:如果您不知道如何处理它,请不要捕获异常。但是让我们来谈谈观察者模式,其中通知者发出关于它的状态变化的事件(或信号),并且听众处理它。通知程序的一个很好的例子是发出“点击”事件的按钮。按钮是否关心谁是听众以及他们做了什么?并不是的。如果你是一个倾听者并且你有这个活动,那么你就有工作要做。如果你不能这样做,你必须处理这个错误或通知用户,因为将异常传递给按钮是没有意义的 - 按钮肯定不知道如何处理监听器作业的错误。而按钮状态转换器(可能是一些消息循环)也没有 - 您的应用程序最终会在Main()中崩溃。

That's how observer pattern works - event emitters don't know what their listeners are doing and there's very little chance they will handle this exception properly.

这就是观察者模式的工作原理 - 事件发射器不知道他们的听众正在做什么,并且他们很少有机会正确处理这个异常。

Also bear in mind, that if your exception handler throws exception, there may be other listeners that won't receive notification and that may lead to undefined application state.

还要记住,如果您的异常处理程序抛出异常,可能会有其他侦听器无法接收通知,并且可能导致未定义的应用程序状态。

So my advice is to catch all exceptions at event handler, but figure out how to handle them there. Or else no one will.

所以我的建议是在事件处理程序中捕获所有异常,但要弄清楚如何在那里处理它们。否则没人会。

#3


My personal prejudice is that not catching exceptions is generally a bad thing. The only "exception" to this rule for me is for simple applciations where the uncaught exception termianates the process, you see the error and fix it.

我个人的偏见是,不捕捉异常通常是一件坏事。对我来说,这个规则的唯一“例外”是简单的应用程序,其中未被捕获的异常终止过程,您会看到错误并修复它。

For multi-threaded apps, if the default behaviour of uncaught exceptions is to zap threads then it seems to me absurd not to catch exceptions. Hence event handlers should not just punt the exception up the stack and hope for the best.

对于多线程应用程序,如果未捕获的异常的默认行为是zap线程,那么在我看来不要捕获异常是荒谬的。因此,事件处理程序不应该只是将异常放在堆栈中并希望最好。

Silently swallowing exceptions is usually bad too, something bad has happended it needs fixing. So perhaps log a message, and then return?

悄悄吞咽异常通常也是不好的,有些不好的东西需要修复。那么也许记录一条消息,然后返回?

#4


You need a contract with the event source on whether the event handler can throw exceptions. For example, if a COM object is an event source this is strictly prohibited - exceptions should never cross COM boundary.

您需要与事件源签订合同,以确定事件处理程序是否可以抛出异常。例如,如果COM对象是事件源,则严格禁止 - 异常不应跨越COM边界。

#5


An event is nothing more than syntactic sugar around a function call, so it makes sense that it would propagate up to the function that raised the event. What, otherwise, should the behaviour be? The exception has to go somewhere.

一个事件只不过是函数调用的语法糖,所以它会传播到引发事件的函数。否则,行为应该是什么?例外必须去某个地方。

#6


It would be the best to think about exception as part of your contract with event listeners.

最好将异常视为与事件监听器签订合同的一部分。

Which means, if the listener is nice and catches its Exceptions (it can do that for known ones), you are OK.

这意味着,如果监听器很好并且捕获了它的异常(它可以为已知的异常做到这一点),那么你就可以了。

For the rest, unknown exceptions, or in Java speech "Runtime exceptions", you need to be ready for them the same way as if they would occur in your code.

对于其余的,未知的异常,或者在Java语音“运行时异常”中,您需要为它们做好准备,就像它们在代码中出现一样。

What I am trying to say is, if you are building a contract with the event listeners, you cannot force them to throw just one exception type (so that you can consume them) and you need to take all Exceptions seriously. After all, they are indications of "wrong states" which you don't want to hide for consumers.

我想说的是,如果你正在与事件监听器建立一个契约,你不能强迫它们抛出一个异常类型(这样你就可以使用它们),你需要认真对待所有异常。毕竟,它们是“错误状态”的迹象,你不想为消费者隐瞒这些状态。

#1


What's your preferred alternative - silently swallowing the exception? I wouldn't like that at all.

什么是你的首选替代方案 - 默默地吞下这个例外?我完全不喜欢这样。

Events are just a way of implementing the observer pattern, really. If a listener throws an exception, I'd absolutely expect that exception to be thrown back to the caller. Any other behaviour I can think of would effectively treat the exception as unimportant. The whole point of exceptions is that when something goes wrong, you find out about it quickly and implicitly. You have to explicitly handle exceptions, so that you don't go on your merry way in a corrupt state without realising it.

实际上,事件只是实现观察者模式的一种方式。如果一个监听器抛出一个异常,我绝对希望将该异常抛回给调用者。我能想到的任何其他行为都会有效地将异常视为不重要。异常的全部意义在于,当出现问题时,您会快速而隐含地发现它。你必须明确地处理异常,这样你就不会在没有意识到的情况下以腐败的状态继续你的快乐方式。

You make a valid point about whose responsibility it is to handle exceptions. Broadly speaking, I find it best to assume that just about anything can throw an exception at any time. Other than special cases where I know a specific exception may occur and I can handle it, I don't usually catch exceptions other than at the top level - except possibly to wrap and rethrow, or log and rethrow.

您提出了一个有效的观点,即处理异常的责任。从广义上讲,我发现最好假设任何事情都可以随时抛出异常。除了我知道可能发生特定异常并且我可以处理它的特殊情况之外,我通常不会捕获除顶层以外的异常 - 除了可能包装和重新抛出,或记录和重新抛出。

Now it's possible that one of your event handlers really shouldn't be throwing an exception - that they haven't really run into an error condition - but what should happen if it's a perfectly reasonable exception which indicates a serious problem? While a program crashing is ugly, it's often better than continuing with some of it malfunctioning, possibly corrupting persisted state etc.

现在你的一个事件处理程序可能真的不应该抛出一个异常 - 它们并没有真正遇到错误状态 - 但是如果它是一个完全合理的异常表明存在严重问题会发生什么呢?虽然程序崩溃是丑陋的,但通常比继续其中一些故障,可能破坏持久状态等更好。

Fundamentally, I don't think the CS/SE field has got error handling "right" yet. I'm not even sure that there is an elegant way of doing the right thing which is simple to express in all situations... but I hope that the current situation isn't as good as it gets.

从根本上说,我认为CS / SE领域还没有“正确”的错误处理。我甚至不确定是否有一种优雅的方式来做正确的事情,这种方式在所有情况下都很容易表达......但我希望目前的情况并不像它那样好。

#2


The main aspect of exception handling discussed here is: do not catch exception if you don't know how to handle it. But let's talk about the observer pattern, where notifier emits event (or signal) about it's state change and listeners handle it. A good example of a notifier is a button emitting 'clicked' event. Does a button care about who are the listeners and what they do? Not really. If you're a listener and you got this event, then you've got a job to do. And if you can't do it, you have to handle this error or inform user, because passing exception to the button makes no sense - button definitely does not know how to handle errors of listener job. And buttons state changer (probably some message loop) does not either - your application will end up at Main() with a crash.

这里讨论的异常处理的主要方面是:如果您不知道如何处理它,请不要捕获异常。但是让我们来谈谈观察者模式,其中通知者发出关于它的状态变化的事件(或信号),并且听众处理它。通知程序的一个很好的例子是发出“点击”事件的按钮。按钮是否关心谁是听众以及他们做了什么?并不是的。如果你是一个倾听者并且你有这个活动,那么你就有工作要做。如果你不能这样做,你必须处理这个错误或通知用户,因为将异常传递给按钮是没有意义的 - 按钮肯定不知道如何处理监听器作业的错误。而按钮状态转换器(可能是一些消息循环)也没有 - 您的应用程序最终会在Main()中崩溃。

That's how observer pattern works - event emitters don't know what their listeners are doing and there's very little chance they will handle this exception properly.

这就是观察者模式的工作原理 - 事件发射器不知道他们的听众正在做什么,并且他们很少有机会正确处理这个异常。

Also bear in mind, that if your exception handler throws exception, there may be other listeners that won't receive notification and that may lead to undefined application state.

还要记住,如果您的异常处理程序抛出异常,可能会有其他侦听器无法接收通知,并且可能导致未定义的应用程序状态。

So my advice is to catch all exceptions at event handler, but figure out how to handle them there. Or else no one will.

所以我的建议是在事件处理程序中捕获所有异常,但要弄清楚如何在那里处理它们。否则没人会。

#3


My personal prejudice is that not catching exceptions is generally a bad thing. The only "exception" to this rule for me is for simple applciations where the uncaught exception termianates the process, you see the error and fix it.

我个人的偏见是,不捕捉异常通常是一件坏事。对我来说,这个规则的唯一“例外”是简单的应用程序,其中未被捕获的异常终止过程,您会看到错误并修复它。

For multi-threaded apps, if the default behaviour of uncaught exceptions is to zap threads then it seems to me absurd not to catch exceptions. Hence event handlers should not just punt the exception up the stack and hope for the best.

对于多线程应用程序,如果未捕获的异常的默认行为是zap线程,那么在我看来不要捕获异常是荒谬的。因此,事件处理程序不应该只是将异常放在堆栈中并希望最好。

Silently swallowing exceptions is usually bad too, something bad has happended it needs fixing. So perhaps log a message, and then return?

悄悄吞咽异常通常也是不好的,有些不好的东西需要修复。那么也许记录一条消息,然后返回?

#4


You need a contract with the event source on whether the event handler can throw exceptions. For example, if a COM object is an event source this is strictly prohibited - exceptions should never cross COM boundary.

您需要与事件源签订合同,以确定事件处理程序是否可以抛出异常。例如,如果COM对象是事件源,则严格禁止 - 异常不应跨越COM边界。

#5


An event is nothing more than syntactic sugar around a function call, so it makes sense that it would propagate up to the function that raised the event. What, otherwise, should the behaviour be? The exception has to go somewhere.

一个事件只不过是函数调用的语法糖,所以它会传播到引发事件的函数。否则,行为应该是什么?例外必须去某个地方。

#6


It would be the best to think about exception as part of your contract with event listeners.

最好将异常视为与事件监听器签订合同的一部分。

Which means, if the listener is nice and catches its Exceptions (it can do that for known ones), you are OK.

这意味着,如果监听器很好并且捕获了它的异常(它可以为已知的异常做到这一点),那么你就可以了。

For the rest, unknown exceptions, or in Java speech "Runtime exceptions", you need to be ready for them the same way as if they would occur in your code.

对于其余的,未知的异常,或者在Java语音“运行时异常”中,您需要为它们做好准备,就像它们在代码中出现一样。

What I am trying to say is, if you are building a contract with the event listeners, you cannot force them to throw just one exception type (so that you can consume them) and you need to take all Exceptions seriously. After all, they are indications of "wrong states" which you don't want to hide for consumers.

我想说的是,如果你正在与事件监听器建立一个契约,你不能强迫它们抛出一个异常类型(这样你就可以使用它们),你需要认真对待所有异常。毕竟,它们是“错误状态”的迹象,你不想为消费者隐瞒这些状态。