Exception, Prepare-For-Rethrow!

Tomáš Holan       01.06.2011       C#       10801 zobrazení

Většina vývojářů je asi obeznámena s tím, jaký rozdíl je mezi throw a thow ex tj. pokud místo: (*)

try
{
    //do something that can throws
}
catch (Exception ex)
{
    //Log exception or something
    throw;   //<--OK, original stack trace is preserved
}

provedeme toto:

try
{
    //do something that can throws
}
catch (Exception ex)
{
    //Log exception or something
    throw ex;   //<--BUG, original stack trace is lost
}

V druhém případě je původně zachycená výjimka znovu vyhozena, čímž přijdeme o původní stack trace informace tj. stack trace pak bude začínat až od nového místa opakovaného vyhození. Zatímco volání pouze throw zajistí správné šíření původní výjimky (ale lze samozřejmě použít pouze uvnitř daného catch bloku).

Pro úplnost ještě další, trochu jiný případ: Jak je to v případě, kdy potřebujeme vyhodit výjimku jinou (jiný datový typ, doplnění informací o aktuálním kontextu apod.)?

try
{
    //do something that can throws
}
catch (Exception ex)
{
    throw new ApplicationException("Operation failed, more details in InnerException.", ex);    //<--Stack trace is preserved in InnerException
}

To také není problém, když to uděláme chytře tj. když do nově zkonstruované výjimky předáme výjimku původní jako InnerException. Tím zajistíme, že ve výpisech (např. přes ToString() na samotné výjimce) apod. bude nový a původní stack trace “složen” dohromady a my o žádnou informaci nepřijdeme.

Tak jo, zatím možná pouze “opáčko”, ale teď pozor! Co když se najde scénář, kdy z nějakého důvodu opravdu potřebujeme opakovaně vyhodit původně zachycenou výjimku a throw přitom použít nelze (tj. opakované vyhození nelze provést v původním catch bloku). I když se sice může na první pohled zdát takový případ obtížně najitelný, již jsem se s několika v praxi setkal. Může se např. jednat o obsluhu chyby asynchronního volání nebo třeba o multi-threadingový scénář, kde je potřeba výjimku předat např. z pracovního vlákna do vlákna hlavního.

Pro takové případy bude velice užitečná extension metoda ExceptionExtensions.PrepareForRethrow(), jejíž implementace (†) vypadá takto:

using System;
using System.Reflection;

namespace System.Diagnostics
{
    internal static class ExceptionExtensions
    {
        #region member varible and default property initialization
        private static readonly MethodInfo prepForRemoting = typeof(Exception).GetMethod("PrepForRemoting", BindingFlags.NonPublic | BindingFlags.Instance);
        #endregion

        #region action methods
        public static Exception PrepareForRethrow(this Exception exception)
        {
            if (exception == null)
            {
                throw new ArgumentNullException("exception");
            }

            prepForRemoting.Invoke(exception, new object[0]);
            return exception;
        }
        #endregion
    }
}

Implementace metody využívá toho, že objekt Exception má sám o sobě již od prvních verzích .NET Frameworku tuto funkci interně zabudovanou a využívanou technologií .NET Remoting (pokud jste se s touto technologií již nesetkali, jedná se o předchůdce WCF). Tato metoda tedy pouze pomoci reflekce volá příslušnou interní funkci (‡).

Metodu PrepareForRethrow() pak stačí na výjimce volat před jejím opakovaném vyhození. Při použití této metody bude ve výpisech původní a nový stack trace oddělen textem "Exception rethrowed at...".

Celé použití pak může vypadat následovně (opět pouze ilustrativní příklad):

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

...

var synchronizationContext = SynchronizationContext.Current;    //UI thread

Task.Factory.StartNew(() =>
    {
        try
        {
            //...
        }
        catch (System.Exception ex)
        {
            synchronizationContext.Post(state => { throw ex.PrepareForRethrow(); }, null);  //<--Rethrow back to original synchronization context
        }
    });

Zde při chybě z pracovního vlákna dojde k “rethrownutí” výjimky do původního synchronizačního kontextu, kterým je zpravidla UI (WPF Dispatcher, Windows Forms apod.), kde pak může být zachycena např. nějakým Application_UnhandledException handlerem apod.


(*) Nutno upozornit, že uvedený příklad je pouze ilustrativní a takového “chytání” obecné výjimky System.Exception není vůbec správné, a je dokonce diskutabilní i právě pro případ pouhého “zalogování” a opakovaného vyhození. (Protože takovýmto catch blokem může být výjimka obecně zachycena i v takovém případě, kdy je z důvodu nějaké kritické chyby nekonzistentní nebo nedefinovaný stav celého aktuálního procesu).

(†) Uvedená implementace je součástí také například knihovny Reactive Extensions for .NET (Rx) (System.CoreEx.dll).

(‡) Z důvodu nutnosti volání interní metody pomoci reflekce není bohužel toto řešení použitelné v technologii Silverlight.

 

hodnocení článku

0 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

RE: Exception, Prepare-For-Rethrow!

Jen pro doplnění: V připravovaném .NET Framweorku 4.5 již bude přímo podpora pro uchování informací o zachycené vyjímce do objektu. Půjde tedy pak jednoduše zachycenou vyjímku opakovaně vyhodit i později mimo catch blok.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

RE: Exception, Prepare-For-Rethrow!

Pozor, obecně se uvádí, že toto je nejčastější neznalost programátorů v .Net a uvádí se, že typické použití je právě špatné "throw ex;".

Jirka

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback