2013-05-18 2 views
83

Ich brauche eine async Methode in einem catch Block aufrufen, bevor wieder die Ausnahme zu werfen (mit seinem Stack-Trace) wie folgt aus:Eine gute Lösung für warten in Versuch/Fang/endlich?

try 
{ 
    // Do something 
} 
catch 
{ 
    // <- Clean things here with async methods 
    throw; 
} 

Aber leider kann man nicht await in einem catch oder finally Block verwenden. Ich habe gelernt, es ist, weil der Compiler keine Möglichkeit hat in einem catch Block zurück zu gehen auszuführen, was nach Ihrer await Anweisung oder so ähnlich ist ...

Ich versuchte Task.Wait() zu verwenden await zu ersetzen, und ich bekam eine Sackgasse. Ich habe im Internet gesucht, wie ich das vermeiden konnte und habe this site gefunden.

Da kann ich nicht die async Methoden ändern, noch weiß ich, ob sie ConfigureAwait(false) verwenden, ich diese Methoden erstellt, die ein Func<Task> nehmen, die ein asynchrones Verfahren beginnt, wenn wir auf einem anderen Thread (zu vermeiden, dass ein Deadlock) und wartet sind für seine Vollendung:

public static void AwaitTaskSync(Func<Task> action) 
{ 
    Task.Run(async() => await action().ConfigureAwait(false)).Wait(); 
} 

public static TResult AwaitTaskSync<TResult>(Func<Task<TResult>> action) 
{ 
    return Task.Run(async() => await action().ConfigureAwait(false)).Result; 
} 

public static void AwaitSync(Func<IAsyncAction> action) 
{ 
    AwaitTaskSync(() => action().AsTask()); 
} 

public static TResult AwaitSync<TResult>(Func<IAsyncOperation<TResult>> action) 
{ 
    return AwaitTaskSync(() => action().AsTask()); 
} 

So ist meine Fragen: glauben Sie, dieser Code in Ordnung ist?

Natürlich, wenn Sie einige Verbesserungen haben oder einen besseren Ansatz kennen, höre ich! :)

+2

Verwendung von 'erwarten' in einem catch-Block ist tatsächlich erlaubt seit C# 6.0 (siehe meine Antwort unten) –

+3

Verwandte ** C# 5.0 ** Fehlermeldungen: *** CS1985 ***: * Kann nicht im Körper eines a erwarten catch-Klausel. * *** CS1984 ***: * Kann im Body einer finally-Klausel nicht abwarten. * – DavidRR

Antwort

163

Sie können die Logik außerhalb des Blocks catch verschieben und die Ausnahme nach Bedarf erneut auslösen, indem Sie ExceptionDispatchInfo verwenden.

static async Task f() 
{ 
    ExceptionDispatchInfo capturedException = null; 
    try 
    { 
     await TaskThatFails(); 
    } 
    catch (MyException ex) 
    { 
     capturedException = ExceptionDispatchInfo.Capture(ex); 
    } 

    if (capturedException != null) 
    { 
     await ExceptionHandler(); 

     capturedException.Throw(); 
    } 
} 

Auf diese Weise, wenn der Anrufer die StackTrace Eigenschaft Ausnahme prüft, wo es noch Aufzeichnungen innerhalb TaskThatFails wurde geworfen.

public static class TryWithAwaitInCatch 
{ 
    public static async Task ExecuteAndHandleErrorAsync(Func<Task> actionAsync, 
     Func<Exception, Task<bool>> errorHandlerAsync) 
    { 
     ExceptionDispatchInfo capturedException = null; 
     try 
     { 
      await actionAsync().ConfigureAwait(false); 
     } 
     catch (Exception ex) 
     { 
      capturedException = ExceptionDispatchInfo.Capture(ex); 
     } 

     if (capturedException != null) 
     { 
      bool needsThrow = await errorHandlerAsync(capturedException.SourceException).ConfigureAwait(false); 
      if (needsThrow) 
      { 
       capturedException.Throw(); 
      } 
     } 
    } 
} 

Man würde es verwenden, wie folgt:

+0

Was ist der Vorteil, 'ExceptionDispatchInfo' anstelle von' Exception' zu behalten (wie in der Antwort von Stephen Cleary)? –

+1

Ich kann es erraten, wenn du entscheidest, die "Exception" zu wiederholen, verlierst du alle vorherigen "StackTrace"? –

+0

@VarvaraKalinina Genau. – hvd

14

Wenn Sie async Fehlerbehandlungsroutinen verwenden müssen, würde ich so etwas wie dies empfehlen:

Exception exception = null; 
try 
{ 
    ... 
} 
catch (Exception ex) 
{ 
    exception = ex; 
} 

if (exception != null) 
{ 
    ... 
} 

Das Problem mit synchron Blockierung auf async Code (unabhängig davon, was Gewinde auf es läuft) ist, dass Sie synchron blockieren. In den meisten Fällen ist es besser, await zu verwenden.

Aktualisierung: Da Sie erneut starten müssen, können Sie ExceptionDispatchInfo verwenden.

+1

Danke aber leider kenne ich diese Methode schon. Das mache ich normalerweise, aber ich kann das hier nicht tun. Wenn ich einfach 'throw exception;' verwende, wird in der 'if'-Anweisung die Stack-Trace verloren gehen. – user2397050

2

Wir hvd's great answer auf die folgende wiederverwendbare Utility-Klasse in unserem Projekt extrahiert

public async Task OnDoSomething() 
    { 
     await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync(
      async() => await DoSomethingAsync(), 
      async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; } 
     ); 
    } 

Fühlen Sie sich frei, um die Namensgebung zu verbessern, hielten wir es absichtlich ausführliche . Beachten Sie, dass der Kontext im Wrapper nicht erfasst werden muss, da er bereits in der Aufruf-Site erfasst ist, daher ConfigureAwait(false).

46

Sie sollten das seit C# 6 wissen.0, dann ist es möglich, await in catch und finally Blöcke zu verwenden, so könnte man dies in der Tat:

try 
{ 
    // Do something 
} 
catch (Exception ex) 
{ 
    await DoCleanupAsync(); 
    throw; 
} 

Die neuen C# 6.0-Funktionen, einschließlich der, die ich gerade erwähnt are listed here oder als Video here.

+0

Unterstützung für [erwarten in Fang/endlich blockiert] (https://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29#C.23_6.0) in C# 6.0 ist auch auf Wikipedia angezeigt. – DavidRR

+2

@DavidRR. Die Wikipedia ist nicht autorisierend. Es ist nur eine andere Website unter Millionen, soweit es das betrifft. – user34660