简体   繁体   English

我可以拥有强大的异常安全性和事件吗?

[英]Can I have strong exception safety and events?

Suppose I have a method which changes the state of an object, and fires an event to notify listeners of this state change: 假设我有一个方法可以更改对象的状态,并触发一个事件以通知侦听器此状态更改:

public class Example
{
   public int Counter { get; private set; }

   public void IncreaseCounter()
   {
      this.Counter = this.Counter + 1;
      OnCounterChanged(EventArgs.Empty);
   }

   protected virtual void OnCounterChanged(EventArgs args)
   {
      if (CounterChanged != null)
         CounterChanged(this,args);
   }

   public event EventHandler CounterChanged;
}

The event handlers may throw an exception even if IncreaseCounter successfully completed the state change. 即使IncreaseCounter成功完成了状态更改,事件处理程序也可能引发异常。 So we do not have strong exception safety here: 因此,我们在这里没有强大的异常安全性

The strong guarantee: that the operation has either completed successfully or thrown an exception, leaving the program state exactly as it was before the operation started. 强有力的保证:操作已成功完成或引发了异常,从而使程序状态与操作开始之前的状态完全相同。

Is it possible to have strong exception safety when you need to raise events? 当您需要引发事件时,是否可以具有强大的异常安全性?

To prevent an exception in a handler from propagating to the event generator, the answer is to manually invoke each item in the MultiCast Delegate (ie the event handler) inside of a try-catch 为了防止处理程序中的异常传播到事件生成器,答案是在try-catch内手动调用MultiCast委托中的每个项目(即事件处理程序)

All handlers will get called, and the exception won't propagate. 所有处理程序都将被调用,并且异常不会传播。

public EventHandler<EventArgs> SomeEvent;

protected void OnSomeEvent(EventArgs args)
{
    var handler = SomeEvent;
    if (handler != null)
    {
        foreach (EventHandler<EventArgs> item in handler.GetInvocationList())
        {
            try
            {
                item(this, args);
            }
            catch (Exception e)
            {
                // handle / report / ignore exception
            }
        }                
    }
}

What remains is for you to implement the logic for what do do when one or more event recipients throws and the others don't. 剩下的就是您实现一个或多个事件接收者抛出而其他事件没有抛出时该做什么的逻辑。 The catch() could catch a specific exception as well and roll back any changes if that is what makes sense, allowing the event recipient to signal the event source that an exceptional situation has occurred. catch()也可以捕获特定的异常,并在合理的情况下回滚所有更改,从而使事件接收者可以向事件源发出信号,表明发生了异常情况。

As others point out, using exceptions as control flow isn't recommended. 正如其他人指出的那样,不建议将异常用作控制流。 If it's truly an exceptional circumstance, then by all means use an exception. 如果确实是例外情况,则一定要使用例外。 If you're getting a lot of exceptions you probably want to use something else. 如果您遇到许多异常,则可能要使用其他东西。

The general pattern you will see for frameworks is: 您将看到的框架的一般模式是:

public class Example
{
   public int Counter { get; private set; }

   public void IncreaseCounter()
   {
      OnCounterChanging(EventArgs.Empty);
      this.Counter = this.Counter + 1;
      OnCounterChanged(EventArgs.Empty);
   }

   protected virtual void OnCounterChanged(EventArgs args)
   {
      if (CounterChanged != null)
         CounterChanged(this, args);
   }

   protected virtual void OnCounterChanging(EventArgs args)
   {
      if (CounterChanging != null)
         CounterChanging(this, args);
   }

   public event EventHandler<EventArgs> CounterChanging;
   public event EventHandler<EventArgs> CounterChanged;
}

If a user would like to throw an exception to prevent the changing of the value then they should be doing it in the OnCounterChanging() event instead of the OnCounterChanged(). 如果用户想抛出一个异常以防止值更改,那么他们应该在OnCounterChanging()事件而不是OnCounterChanged()事件中进行更改。 By definition of the name (past tense, suffix of -ed) that implies the value has been changed. 通过名称的定义(过去时,-ed的后缀),表示该值已更改。

Edit: 编辑:

Note that you generally want to stay away from copious amounts of extra try..catch/finally blocks as exception handlers (including try..finally) are expensive depending on the language implementation. 请注意,您通常希望避免大量额外的try..catch / finally块,因为异常处理程序(包括try..finally)的昂贵取决于语言实现。 ie The win32 stack framed model or the PC-mapped exception model both have their pros and cons, however if there are too many of these frames they will both be costly (either in space or execution speed). 即,win32堆栈框架模型或PC映射的异常模型各有优缺点,但是,如果这些框架太多,则两者都会很昂贵(无论是空间还是执行速度)。 Just another thing to keep in mind when creating a framework. 创建框架时要记住的另一件事。

Well, you could compensate if it was critical: 好吧,如果这很关键,您可以补偿:

public void IncreaseCounter()
{
  int oldCounter = this.Counter;
  try {
      this.Counter = this.Counter + 1;
      OnCounterChanged(EventArgs.Empty);
  } catch {
      this.Counter = oldCounter;
      throw;
  }
}

Obviously this would be better if you can talk to the field (since assigning a field won't error). 显然,如果您可以与该领域进行交流,那会更好(因为分配一个领域不会出错)。 You could also wrap this up in bolierplate: 您也可以将其包装在bolierplate中:

void SetField<T>(ref T field, T value, EventHandler handler) {
    T oldValue = field;
    try {
         field = value;
         if(handler != null) handler(this, EventArgs.Empty);
    } catch {
         field = oldField;
         throw;
    }
}

and call: 并致电:

SetField(ref counter, counter + 1, CounterChanged);

or something similar... 或类似的东西

Will not simply swapping the order of the two lines in IncreaseCounter enforcestrong exception safety for you? 难道不可以简单地在IncreaseCounter交换两行的顺序来为您实施强大的异常安全性吗?

Or if you need to increment Counter before you raise the event: 或者,如果您需要在引发事件之前增加Counter

public void IncreaseCounter()
{
    this.Counter += 1;

    try
    {
        OnCounterChanged(EventArgs.Empty);
    }
    catch
    {
        this.Counter -= 1;

        throw;
    }
}

In a situation where you have more complex state changes (side effects) going on, then it becomes rather more complicated (you might have to clone certain objects for example), but in for this example it would seem quite easy. 在状态变化(副作用)不断发生的情况下,情况变得更加复杂(例如,您可能必须克隆某些对象),但是在此示例中,这似乎很容易。

In this case, I think you should take a cue from Workflow Foundation. 在这种情况下,我认为您应该从Workflow Foundation中获得启发。 In the OnCounterChanged method, surround the call to the delegate with a generic try-catch block. 在OnCounterChanged方法中,使用通用try-catch块包围对委托的调用。 In the catch handler, you'll have to do what is effectively a compensating action -- rolling back the counter. 在catch处理程序中,您必须执行有效的补偿操作-还原计数器。

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

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