2015-12-03 2 views
0

В этом примере у меня есть два подписчика на мое событие. Один из подписчиков вызывает исключение, но я хотел бы предотвратить отказ всех подписчиков, когда только один из них берет на себя Исключение. Выражение try-catch недостаточно для того, чтобы зафиксировать исключение класса Dog, это также приводит к сбою класса Cat.Исключения для обработки в событиях

using System; 

namespace EventsExample 
{ 
    class BreadWinnerEventArgs : EventArgs 
    { 
     public string Name { get; set; } 
    } 

    class BreadWinner // publisher 
    { 
     public event EventHandler<BreadWinnerEventArgs> ArrivedHome; // 2. 

     public void Action(BreadWinnerEventArgs args) 
     { 
      Console.WriteLine("Papa says: I'm at home!"); 
      OnArriveHome(args); 
     } 

     protected virtual void OnArriveHome(BreadWinnerEventArgs args) 
     { 
      if (ArrivedHome != null) 
      { 
       foreach (EventHandler<BreadWinnerEventArgs> handler in ArrivedHome.GetInvocationList()) 
       { 
        try 
        { 
         var t = ArrivedHome; // publisher uses sames signature as the delegate 
         if (t != null) 
          t(this, args); 
        } 
        catch (Exception e) 
        { 
         Console.WriteLine("Error in the handler {0}: {1}", handler.Method.Name, e.Message); 
        } 
       } 
      } 
     } 
    } 

    class Dog 
    { 
     public void OnArrivedHome(object source, BreadWinnerEventArgs e) 
     { 
      throw new Exception(); 
      Console.WriteLine(String.Format("Dog says: Whoof {0}!", e.Name)); 
     } 
    } 
    class Cat 
    { 
     public void OnArrivedHome(object source, BreadWinnerEventArgs e) 
     { Console.WriteLine(String.Format("Cat hides from {0}", e.Name)); } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      BreadWinner papa = new BreadWinner(); // publisher 
      Dog dog = new Dog(); // subscriber 
      Cat cat = new Cat(); 
      papa.ArrivedHome += dog.OnArrivedHome; // subscription 
      papa.ArrivedHome += cat.OnArrivedHome; 

      papa.Action(new BreadWinnerEventArgs() { Name = "Papa" }); 
      Console.Read(); 
     } 
    } 
} 
+0

Нет, если обработчик события вызывает исключение, шоу прекращается. Вы абсолютно не знаете, как справиться с этим исключением, вы ничего не знаете о коде, который не прошел. И поэтому он никогда не сможет восстановить состояние. –

ответ

2

Вы почти были, вы просто где с помощью t, где вы должны были с помощью handler, вам также, где с помощью ArrivedHome, где вы должны были с помощью t. Я также изменил код, чтобы обернуть все исключения и делегата, который вызвал их в настраиваемое исключение, а затем обернуть их в исключение aggragate и повысить код.

protected virtual void OnArriveHome(BreadWinnerEventArgs args) 
{ 
    var t = ArrivedHome; // publisher uses sames signature as the delegate 
    if (t != null) 
    { 
     var exceptions = new List<Exception>(); 
     foreach (EventHandler<BreadWinnerEventArgs> handler in t.GetInvocationList()) 
     { 
      try 
      { 
       try 
       { 
        handler(this, args); 
       } 
       catch (Exception e) 
       { 
        Console.WriteLine("Error in the handler {0}: {1}", handler.Method.Name, e.Message); 

        throw new DelegateException(handler, e, this, args); //Throw the exception to capture the stack trace. 
       } 
      } 
      catch (DelegateException e) 
      { 
       exceptions.Add(e); 
      } 
     } 
     if (exceptions.Count > 0) 
     { 
      throw new AggregateException(exceptions); 
     } 
    } 
} 

///Elsewhere 
sealed class DelegateException : Exception 
{ 
    public Delegate Handler { get; } 
    public object[] Args { get; } 

    public DelegateException(Delegate handler, Exception innerException, params object[] args) : base("A delegate raised an error when called.", innerException) 
    { 
     Handler = handler; 
     Args = args; 
    } 
} 

Однако я не думаю, что вы действительно должны делать это, это отклоняется от «ожидаемого поведения» и может поймать другие программист врасплох, если они должны потреблять свои классы, которые делают это.

+0

Довольно точно, что я написал, немного быстрее с клавиатуры, + –

+0

Хороший ответ, но я не понимаю, что такое «ожидаемое поведение». Для меня это хороший вариант, чтобы запретить подписчикам «ломать» поток другим подписчикам на мероприятие. – Rober

+0

«Обычные» события прерывают поток при возникновении исключения. Это то, что люди ожидают, поэтому ваш код не выполняет «ожидаемое поведение». –

1

Я не говорю, что вы должны сделать это, но это один из способов справиться с этим:

protected virtual void OnArriveHome(BreadWinnerEventArgs args) 
{ 
    var handler = ArrivedHome; 

    if (handler == null) 
     return; 

    foreach (var subscriber in handler.GetInvocationList()) 
    { 
     try 
     { 
      subscriber(this, args); 
     } 
     catch (Exception ex) 
     { 
      //You can, and probably should, remove the handler from the list here 
     } 
    } 
} 

Это позволяет вызывать каждого из абонентов в отдельности, а не в качестве группы, и поймать исключение, когда один из них бросает. Проблема, с которой я сталкиваюсь, заключается в том, что вы действительно не можете знать, что сломалось, или сделать что-нибудь, чтобы исправить это. Все, что вы можете сделать, это журнал и необязательно удалить этот обработчик событий, чтобы в следующий раз вы не выбрали его.

Удаление обработчика также может быть плохой практикой, поскольку может быть трудно проследить, почему ранее назначенный обработчик теперь не назначен.

Смежные вопросы