2013-05-18 12 views
83

Ho bisogno di chiamare un metodo async in un blocco catch prima di lanciare di nuovo l'eccezione (con il suo stack trace) come questo:Una buona soluzione per attendere in try/catch/finally?

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

Ma purtroppo non è possibile utilizzare await in un blocco catch o finally. Ho imparato è perché il compilatore non ha alcun modo per tornare indietro in un blocco catch da eseguire ciò che è dopo la vostra istruzione await o qualcosa del genere ...

ho cercato di usare Task.Wait() per sostituire await e ho ottenuto un deadlock. Ho cercato sul Web come evitare ciò e ho trovato this site.

Non potendo cambiare i metodi async né so se usano ConfigureAwait(false), ho creato questi metodi che prendono un Func<Task> che avvia un metodo asincrono volta siamo su un thread diverso (per evitare una situazione di stallo) e attende per il suo completamento:

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()); 
} 

Quindi le mie domande sono: Pensi che questo codice sia a posto?

Ovviamente, se si dispone di alcuni miglioramenti o si conosce un approccio migliore, sto ascoltando! :)

+2

L'utilizzo di 'await' in un blocco catch è effettivamente consentito poiché C# 6.0 (vedere la mia risposta di seguito) –

+3

Related ** C# 5.0 ** messaggi di errore: *** CS1985 ***: * Impossibile attendere nel corpo di un clausola catch. * *** CS1984 ***: * Impossibile attendere nel corpo di una clausola finally. * – DavidRR

risposta

163

È possibile spostare la logica all'esterno del blocco catch e ripetere l'eccezione dopo, se necessario, utilizzando ExceptionDispatchInfo.

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

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

     capturedException.Throw(); 
    } 
} 

In questo modo, quando il chiamante ispeziona la proprietà del eccezione StackTrace, ancora record in cui all'interno TaskThatFails è stato gettato.

+0

Qual è il vantaggio di mantenere 'ExceptionDispatchInfo' invece di' Exception' (come nella risposta di Stephen Cleary)? –

+1

Posso immaginare che se decidi di rilanciare l'eccezione, perdi tutto il precedente 'StackTrace'? –

+0

@VarvaraKalinina Esattamente. – hvd

14

Se è necessario utilizzare async gestori di errori, io consiglierei qualcosa di simile:

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

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

Il problema con il blocco in modo sincrono su async codice (indipendentemente da ciò filo su cui sta girando) è che si stai bloccando in modo sincrono. Nella maggior parte degli scenari, è meglio usare await.

Aggiornamento: Poiché è necessario riavviare, è possibile utilizzare ExceptionDispatchInfo.

+1

Grazie, ma purtroppo conosco già questo metodo. Questo è quello che faccio normalmente, ma non posso farlo qui. Se uso semplicemente 'throw exception;' nell'istruzione 'if' la traccia dello stack andrà persa. – user2397050

2

abbiamo estratto hvd's great answer al seguente classe di utilità riutilizzabile nel nostro progetto:

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(); 
      } 
     } 
    } 
} 

Si potrebbe usarlo come segue:

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

Sentitevi liberi di migliorare la denominazione, abbiamo tenuto volutamente prolisso . Si noti che non è necessario acquisire il contesto all'interno del wrapper poiché è già stato acquisito nel sito di chiamata, quindi ConfigureAwait(false).

46

Si dovrebbe sapere che dal momento che C# 6.0, è possibile utilizzare await in catch e finally blocchi, quindi in realtà si potrebbe fare questo:

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

Le nuove funzionalità C# 6.0, tra cui quello che ho appena menzionato are listed here o come un video here.

+0

Il supporto per [attendere nei blocchi catch/finally] (https://en.wikipedia.org/wiki/C_Sharp_%28programming_language%29#C.23_6.0) in C# 6.0 è indicato anche su Wikipedia. – DavidRR

+2

@DavidRR. la Wikipedia non è autoritaria. È solo un altro sito web tra milioni per quanto riguarda questo. – user34660

Problemi correlati