2008-11-10 12 views
34

W aplikacji, nad którą pracuję, każdy błąd logiki biznesowej powoduje zgłoszenie wyjątku, a kod wywołujący obsługuje wyjątek. Ten wzór jest używany w całej aplikacji i działa dobrze.Zgłaszanie wielu wyjątków w .Net/C#

Mam sytuację, w której będę próbował wykonać szereg zadań biznesowych z poziomu warstwy biznesowej. Wymaganie to polega na tym, że awaria jednego zadania nie powinna spowodować zakończenia procesu. Inne zadania powinny nadal być możliwe do wykonania. Innymi słowy, nie jest to operacja atomowa. Problem polega na tym, że pod koniec operacji chcę powiadomić kod wywołujący, że wystąpił wyjątek lub wyjątki, zgłaszając wyjątek. Rozważmy następujący psuedo-fragment kodu:

function DoTasks(MyTask[] taskList) 
{ 
    foreach(MyTask task in taskList) 
    { 
    try 
    { 
     DoTask(task); 
    } 
    catch(Exception ex) 
    { 
     log.add(ex); 
    } 
    } 

    //I want to throw something here if any exception occurred 
} 

Co mam rzucać? Napotkałem ten wzorzec już w mojej karierze. W przeszłości zachowałem listę wszystkich wyjątków, a następnie wyrzucono wyjątek zawierający wszystkie złapane wyjątki. To nie wydaje się być najbardziej eleganckim podejściem. Ważne jest zachowanie jak największej liczby szczegółów od każdego wyjątku do chwili obecnej do kodu wywołującego.

Myśli?


Edytuj: Rozwiązanie musi być napisane w .Net 3.5. Nie mogę korzystać z żadnych bibliotek beta, lub wyjątek AggregateException w .Net 4.0, o którym wspomniała Bradley Grainger (poniżej) byłby dobrym rozwiązaniem dla wyjątków kolekcji do rzucania.

+0

Po prostu wyrzuć wyjątek. Po jego zalogowaniu (i zrobieniu czegokolwiek innego), wyrzuć go ponownie, aby wypluł stos. – Kon

+1

Który wyjątek? Mój scenariusz zakłada wiele wyjątków. –

+0

Właśnie zaktualizowałem swoją odpowiedź, aby zauważyć, że można klonować wyjątek AggregateException na .NET 3.5 w sposób, który ułatwia aktualizację do prawdziwego typu .NET 4.0, kiedy zostanie wydany. Nadal musisz napisać kod (aby sklonować klasę), ale jest kompatybilny z przyszłymi. –

Odpowiedz

37

Task Parallel Library extensions NET (która will become part of .NET 4.0) follow wzór proponowanych w innych odpowiedzi: zbierając wszystkie wyjątki, które zostały wyrzucone w klasie AggregateException.

Przez zawsze rzucanie tego samego typu (niezależnie od tego, czy istnieje wyjątek od pracy podrzędnej, czy wielu), kod wywołujący wyjątek jest łatwiejszy do napisania.

W CTP .NET 4.0, AggregateException ma publiczny konstruktor (który zajmuje IEnumerable<Exception>); może to być dobry wybór dla twojej aplikacji.

Jeśli kierujesz aplikację .NET 3.5, rozważ sklonowanie części klasy System.Threading.AggregateException, które potrzebujesz w swoim własnym kodzie, np. Niektórych konstruktorach i właściwości InnerExceptions. (Możesz umieścić swój klon w przestrzeni nazw System.Threading w swoim zespole, co może spowodować zamieszanie, jeśli ujawnisz go publicznie, ale później sprawi, że uaktualnienie do wersji 4.0 będzie łatwiejsze.) Po zwolnieniu .NET 4.0 powinieneś być w stanie "uaktualnić" do typu Framework poprzez usunięcie pliku źródłowego zawierającego twój klon z twojego projektu, zmianę projektu na docelową nową wersję struktury i przebudowanie. Oczywiście, jeśli to zrobisz, musisz uważnie śledzić zmiany w tej klasie, ponieważ Microsoft wypuszcza nowe CTPs, aby twój kod nie stał się niekompatybilny. (Na przykład wygląda to na użyteczną klasę ogólnego zastosowania i można ją przenieść z System.Threading do System.) W najgorszym przypadku można zmienić nazwę typu i przenieść ją z powrotem do własnej przestrzeni nazw (to bardzo proste z większość narzędzi do refaktoryzacji).

+0

Jak żałuję, że już nie mieliśmy tej biblioteki. Trochę się z tym pogubiłem i to jest niesamowite. Niestety, kierujemy się na .Net 3.5. –

+0

Właściwie możesz teraz pobrać bibliotekę ParallelFx. http://www.microsoft.com/downloads/details.aspx?FamilyId=348F73FD-593D-4B3C-B055-694C50D2B0F3&displaylang=en – Will

+0

Nie wprowadziłbym go do produkcji. To jest CTP. –

0

No super eleganckie rozwiązanie tutaj jednak kilka pomysłów:

  • przekazać funkcję błędu uchwytu jako argument do DoTasks więc użytkownik może zdecydować, czy kontynuować
  • zastosować śledzenie do dziennika błędów, które występują
  • złączyć wiadomości od innych wyjątków w komunikacie wiązki wyjątkiem jest
2

Możesz chcieć użyć BackgroundWorker to zrobić dla ciebie. Automatycznie przechwytuje i przedstawia wszelkie wyjątki po zakończeniu, które można następnie wyrzucić lub zaloguj się lub zrobić z czymkolwiek. Otrzymujesz także korzyści wynikające z wielowątkowości.

BackgroundWorker jest ładny otoki wokół delegata asynchronous programming model.

+1

To dobry pomysł, ale problem agregacji i ponownego zagospodarowania wciąż istnieje. Ponadto mój kod psuedo to uproszczenie rzeczywistego scenariusza wielowątkowego, w którym wiele wątków można pobrać z wątków działających w tle. Po prostu nie chciałem pomylić problemu z tymi informacjami. –

4

Można utworzyć własny wyjątek, który sam posiada kolekcję wyjątki. Następnie, w swoim bloku Catch, po prostu dodaj go do tej kolekcji. Po zakończeniu procesu sprawdź, czy liczba wyjątków jest> 0, a następnie wyrzuć niestandardowy wyjątek.

+1

Taka jest odpowiedź zawarta w samym pytaniu. – asterite

13

Dwa sposoby na górze mojej głowie będzie albo dokonać niestandardowy wyjątek i dodać wyjątki do tej klasy i rzucać że koniec:

public class TaskExceptionList : Exception 
{ 
    public List<Exception> TaskExceptions { get; set; } 
    public TaskExceptionList() 
    { 
     TaskExceptions = new List<Exception>(); 
    } 
} 

    public void DoTasks(MyTask[] taskList) 
    { 
     TaskExceptionList log = new TaskExceptionList(); 
     foreach (MyTask task in taskList) 
     { 
      try 
      { 
       DoTask(task); 
      } 
      catch (Exception ex) 
      { 
       log.TaskExceptions.Add(ex); 
      } 
     } 

     if (log.TaskExceptions.Count > 0) 
     { 
      throw log; 
     } 
    } 

lub return true lub false, jeśli zadania nie powiodło się i mieć zmienna "Out List".

public bool TryDoTasks(MyTask[] taskList, out List<Exception> exceptions) 
    { 
     exceptions = new List<Exception>(); 
     foreach (MyTask task in taskList) 
     { 
      try 
      { 
       DoTask(task); 
      } 
      catch (Exception ex) 
      { 
       exceptions.Add(ex); 
      } 
     } 

     if (exceptions.Count > 0) 
     { 
      return false; 
     } 
     else 
     { 
      exceptions = null; 
      return true; 
     } 
    } 
+0

Twoje drugie rozwiązanie wydaje mi się właściwe. –

+1

Podoba mi się drugie rozwiązanie, ale projekt, nad którym pracuję, ma "wyjątek rzucania wyjątku dla łamania reguł biznesowych" wbudowany wszędzie. To naprawdę naruszyłoby ten wzorzec. –