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

时间:2022-02-17 02:25:34

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. So we do not have strong exception safety here:

即使IncreaseCounter成功完成状态更改,事件处理程序也可能抛出异常。所以我们这里没有强大的异常安全性:

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?

当您需要举办活动时,是否有可能拥有强大的异常安全性?

5 个解决方案

#1


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 (i.e. the event handler) inside of a try-catch

为了防止处理程序中的异常传播到事件生成器,答案是手动调用try-catch内的MultiCast Delegate(即事件处理程序)中的每个项目

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.

正如其他人指出的那样,不建议使用异常作为控制流。如果它确实是一种特殊情况,那么一定要使用例外。如果你得到很多例外,你可能想要使用别的东西。

#2


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(). By definition of the name (past tense, suffix of -ed) that implies the value has been changed.

如果用户想要抛出异常以防止更改值,那么他们应该在OnCounterChanging()事件而不是OnCounterChanged()中执行此操作。通过定义名称(过去时,后缀-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. i.e. 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). Just another thing to keep in mind when creating a framework.

请注意,您通常希望远离大量额外的try..catch / finally块,因为异常处理程序(包括try..finally)很昂贵,具体取决于语言实现。即win32堆栈框架模型或PC映射异常模型都有它们的优点和缺点,但是如果这些框架太多它们都将是昂贵的(无论是空间还是执行速度)。在创建框架时要记住另一件事。

#3


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...

或类似的东西......

#4


In this case, I think you should take a cue from Workflow Foundation. In the OnCounterChanged method, surround the call to the delegate with a generic try-catch block. In the catch handler, you'll have to do what is effectively a compensating action -- rolling back the counter.

在这种情况下,我认为你应该从Workflow Foundation获得一个提示。在OnCounterChanged方法中,使用通用try-catch块包围对委托的调用。在catch处理程序中,你必须做有效的补偿操作 - 回滚计数器。

#5


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:

或者,如果您需要在举起活动之前增加计数器:

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.

在你有更复杂的状态变化(副作用)的情况下,它变得相当复杂(你可能必须克隆某些对象),但在这个例子中,它看起来很容易。

#1


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 (i.e. the event handler) inside of a try-catch

为了防止处理程序中的异常传播到事件生成器,答案是手动调用try-catch内的MultiCast Delegate(即事件处理程序)中的每个项目

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.

正如其他人指出的那样,不建议使用异常作为控制流。如果它确实是一种特殊情况,那么一定要使用例外。如果你得到很多例外,你可能想要使用别的东西。

#2


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(). By definition of the name (past tense, suffix of -ed) that implies the value has been changed.

如果用户想要抛出异常以防止更改值,那么他们应该在OnCounterChanging()事件而不是OnCounterChanged()中执行此操作。通过定义名称(过去时,后缀-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. i.e. 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). Just another thing to keep in mind when creating a framework.

请注意,您通常希望远离大量额外的try..catch / finally块,因为异常处理程序(包括try..finally)很昂贵,具体取决于语言实现。即win32堆栈框架模型或PC映射异常模型都有它们的优点和缺点,但是如果这些框架太多它们都将是昂贵的(无论是空间还是执行速度)。在创建框架时要记住另一件事。

#3


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...

或类似的东西......

#4


In this case, I think you should take a cue from Workflow Foundation. In the OnCounterChanged method, surround the call to the delegate with a generic try-catch block. In the catch handler, you'll have to do what is effectively a compensating action -- rolling back the counter.

在这种情况下,我认为你应该从Workflow Foundation获得一个提示。在OnCounterChanged方法中,使用通用try-catch块包围对委托的调用。在catch处理程序中,你必须做有效的补偿操作 - 回滚计数器。

#5


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:

或者,如果您需要在举起活动之前增加计数器:

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.

在你有更复杂的状态变化(副作用)的情况下,它变得相当复杂(你可能必须克隆某些对象),但在这个例子中,它看起来很容易。