J'ai besoin d'appeler une méthode async
dans un bloc catch
avant de relancer l'exception (avec sa trace de pile) comme ceci:
try
{
// Do something
}
catch
{
// <- Clean things here with async methods
throw;
}
Mais malheureusement, vous ne pouvez pas utiliser await
dans un bloc catch
ou finally
. J'ai appris que c'est parce que le compilateur n'a aucun moyen de revenir dans un bloc catch
pour exécuter ce qui se trouve après votre instruction await
ou quelque chose comme ça ...
J'ai essayé d'utiliser Task.Wait()
pour remplacer await
et je me suis retrouvé dans une impasse. J'ai cherché sur le Web comment je pouvais éviter cela et trouvé ce site .
Comme je ne peux pas changer les méthodes async
ni savoir s'ils utilisent ConfigureAwait(false)
, j'ai créé ces méthodes qui prennent un Func<Task>
Qui démarre une méthode async une fois que nous sommes sur un autre thread (pour éviter un blocage) et attend son achèvement:
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());
}
Donc, ma question est la suivante: pensez-vous que ce code est correct?
Bien sûr, si vous avez des améliorations ou connaissez une meilleure approche, je vous écoute! :)
Vous pouvez déplacer la logique en dehors du bloc catch
et rediffuser l'exception après, si nécessaire, en utilisant 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();
}
}
Ainsi, lorsque l'appelant inspecte la propriété StackTrace
de l'exception, il enregistre quand même où il a été jeté dans TaskThatFails
.
Vous devriez savoir que depuis C # 6.0, il est possible d'utiliser await
dans les blocs catch
et finally
afin que vous puissiez en fait faire ceci:
try
{
// Do something
}
catch (Exception ex)
{
await DoCleanupAsync();
throw;
}
Les nouvelles fonctionnalités de C # 6.0, y compris celle que je viens de mentionner sont répertoriées ici ou sous forme de vidéo ici .
Si vous devez utiliser les gestionnaires d’erreur async
, je vous conseille quelque chose comme ceci:
Exception exception = null;
try
{
...
}
catch (Exception ex)
{
exception = ex;
}
if (exception != null)
{
...
}
Le problème avec le blocage synchrone sur le code async
(quel que soit le thread sur lequel il est exécuté) est que vous bloquez de manière synchrone. Dans la plupart des scénarios, il est préférable d'utiliser await
.
Mise à jour: Puisque vous devez rediffuser, vous pouvez utiliser ExceptionDispatchInfo
.
Nous avons extrait la bonne réponse de hvd à la classe d'utilitaires réutilisable suivante de notre projet:
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();
}
}
}
}
On l'utiliserait comme suit:
public async Task OnDoSomething()
{
await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync(
async () => await DoSomethingAsync(),
async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; }
);
}
N'hésitez pas à améliorer la dénomination, nous l'avons délibérément conservée. Notez qu'il n'est pas nécessaire de capturer le contexte à l'intérieur du wrapper car il est déjà capturé dans le site d'appels, d'où ConfigureAwait(false)
.