Klasa ExceptionDispatchInfo w .NET 4.5

W wersji .NET 4.5 pojawiła się klasa ExceptionDispatchInfo, która nie jest zbyt znana. W większości przypadków nie jest na szczęście potrzebna, ale np. w wielowątkowości może ułatwić bardzo specyficzne zadania wyrzucania i filtrowania wyjątków.

Klasyczny przykład użycia to zebranie w kolekcji różnych wyjątków, a następnie wyrzucenie ich ponownie (prawdopodobnie po nałożeniu odpowiedniego filtra) w jakimś innym miejscu. Taki scenariusz to jest dokładnie sytuacja w której klasa została użyta w TPL i w konstrukcji await\async.

Załóżmy, że mamy kilka metod:

private static  void Throw()
{
  Throw2();
}

private static void Throw2()
{
  Throw3();
}

private static void Throw3()
{
  throw new NotImplementedException();
}

Chcemy wywoływać Throw, która następnie wywoła kilka metod pod rząd, wywołując na końcu wyjątek. Sytuacja wielowątkowa może wyglądać następująco:

var exceptions = new ConcurrentBag<ExceptionDispatchInfo>();

ThreadPool.QueueUserWorkItem(_ =>
{
 try
 {
     Throw();
 }
 catch (Exception ex)
 {
     ExceptionDispatchInfo exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
     exceptions.Add(exceptionDispatchInfo);
 }
});

Jak widzimy, w kodzie tylko zbieramy wyjątki i nie wyrzucamy ich od razu. Taka sytuacja ma często miejsce w TPL, na przykład w przypadku równoległych pętli.

ExceptionDispatchInfo ma metodę Capture, która owinie odpowiednio wyjątek. Dzięki temu nie stracimy informacji o StackTrace.

Na końcu, w kodzie zbiorczym, chcemy wyjątek wyrzucić ponownie, ale koniecznie z zachowaniem oryginalnego stackstrace. W praktyce prawdopodobnie dokonujemy w tym miejscu również jakiś filtrów. Najprostszy przykład może wyglądać następująco:

foreach (ExceptionDispatchInfo exceptionDispatchInfo in exceptions)
{

 try
 {
     exceptionDispatchInfo.Throw();
 }
 catch (Exception ex)
 {
     Console.WriteLine("{0}", ex);
 }
}

ExceptionDispatchInfo zatem zawiera dwie najważniejsze metody:

  1. Capture – przechowanie wyjątku
  2. Throw – wyrzucenie go podobnie

Wygenerowany wyjątek będzie zawierał stary (oryginalny) stacktrace:

image

Innymi słowy, jeśli chcemy wyrzucić wyjątek ponownie w innym miejscu, bez modyfikacji StackTrace (np. poprzez WrappedException), możemy skorzystać z przedstawionej klasy.

Leave a Reply

Your email address will not be published.