web-dev-qa-db-fra.com

Comment appeler en toute sécurité une méthode asynchrone en C #

J'ai une méthode async qui ne renvoie aucune donnée:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

J'appelle cela d'une autre méthode qui renvoie des données:

public string GetStringData()
{
    MyAsyncMethod(); // this generates a warning and swallows exceptions
    return "hello world";
}

L'appel de MyAsyncMethod() sans l'attendre provoque un avertissement " cet appel n'étant pas attendu, la méthode actuelle continue de s'exécuter avant la fin de l'appel }" dans Visual Studio. Sur la page de cet avertissement, il est indiqué:

Vous devez envisager de supprimer l'avertissement uniquement si vous êtes certain de ne pas vouloir attendre la fin de l'appel asynchrone et que la méthode appelée ne déclenche aucune exception.

Je suis sûr que je ne veux pas attendre que l'appel soit terminé; Je n'ai pas besoin ou le temps pour. Mais l'appel pourrait générer des exceptions.

Je suis tombé sur ce problème à quelques reprises et je suis sûr que c'est un problème commun qui doit avoir une solution commune.

Comment appeler en toute sécurité une méthode asynchrone sans attendre le résultat?

Mettre à jour:

Pour les personnes suggérant que j'attends juste le résultat, il s'agit d'un code qui répond à une demande Web sur notre service Web (API Web ASP.NET). En attente dans un contexte d'interface utilisateur conserve le fil de l'interface utilisateur libre, mais l'attente dans un appel de requête Web attend que la tâche se termine avant de répondre à la requête, ce qui augmente les temps de réponse sans raison.

231
George Powell

Si vous voulez obtenir l'exception "de manière asynchrone", vous pouvez faire:

  MyAsyncMethod().
    ContinueWith(t => Console.WriteLine(t.Exception),
        TaskContinuationOptions.OnlyOnFaulted);

Cela vous permettra de traiter une exception sur un thread autre que le thread "principal". Cela signifie que vous n'avez pas à "attendre" l'appel de MyAsyncMethod() du thread qui appelle MyAsyncMethod; mais, vous permet toujours de faire quelque chose avec une exception - mais seulement si une exception se produit.

Mettre à jour:

techniquement, vous pourriez faire quelque chose de similaire avec await:

try
{
    await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
    Trace.WriteLine(ex);
}

... ce qui serait utile si vous deviez utiliser spécifiquement try/catch (ou using) mais je trouve que ContinueWith est un peu plus explicite car vous devez savoir ce que ConfigureAwait(false) signifie.

147
Peter Ritchie

Vous devez d’abord envisager de créer GetStringData une méthode async et de l’avoir await la tâche renvoyée par MyAsyncMethod.

Si vous êtes absolument certain que vous n'avez pas besoin de gérer les exceptions de MyAsyncMethod ou lorsque son exécution est terminée, vous pouvez le faire:

public string GetStringData()
{
  var _ = MyAsyncMethod();
  return "hello world";
}

BTW, ce n'est pas un "problème commun". Il est très rare de vouloir exécuter du code sans se soucier de savoir s'il se termine et sans se soucier de sa réussite.

Mise à jour:

Puisque vous êtes sur ASP.NET et que vous souhaitez revenir plus tôt, vous pouvez trouver mon article de blog sur le sujet utile . Cependant, ASP.NET n'a pas été conçu pour cela et il n'y a aucune garantie que votre code s'exécutera après le renvoi de la réponse. ASP.NET fera de son mieux pour le laisser fonctionner, mais il ne peut pas le garantir.

Donc, c’est une bonne solution pour quelque chose de simple, comme de jeter un événement dans un journal où cela n’a aucune importance si vous en perdez quelques-uns ici et là. Ce n'est pas une bonne solution pour tout type d'opérations critiques. Dans ces situations, vous devez adopter une architecture plus complexe, avec un moyen persistant de sauvegarder les opérations (par exemple, files d'attente Azure, MSMQ) et un processus en arrière-plan distinct (par exemple, Azure Worker Rôle, service Win32) pour les traiter.

51
Stephen Cleary

La réponse de Peter Ritchie était ce que je voulais, et l'article de Stephen Cleary à propos du retour anticipé dans ASP.NET a été très utile.

Cependant, comme problème plus général (non spécifique à un contexte ASP.NET), l’application console suivante illustre l’utilisation et le comportement de la réponse de Peter à l’aide de Task.ContinueWith(...).

static void Main(string[] args)
{
  try
  {
    // output "hello world" as method returns early
    Console.WriteLine(GetStringData());
  }
  catch
  {
    // Exception is NOT caught here
  }
  Console.ReadLine();
}

public static string GetStringData()
{
  MyAsyncMethod().ContinueWith(OnMyAsyncMethodFailed, TaskContinuationOptions.OnlyOnFaulted);
  return "hello world";
}

public static async Task MyAsyncMethod()
{
  await Task.Run(() => { throw new Exception("thrown on background thread"); });
}

public static void OnMyAsyncMethodFailed(Task task)
{
  Exception ex = task.Exception;
  // Deal with exceptions here however you want
}

GetStringData() revient tôt sans attendre MyAsyncMethod() et les exceptions générées dans MyAsyncMethod() sont traitées dans OnMyAsyncMethodFailed(Task task) et not dans la try/catch autour de GetStringData()

39
George Powell

Je me retrouve avec cette solution:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

public string GetStringData()
{
    // Run async, no warning, exception are catched
    RunAsync(MyAsyncMethod()); 
    return "hello world";
}

private void RunAsync(Task task)
{
    task.ContinueWith(t =>
    {
        ILog log = ServiceLocator.Current.GetInstance<ILog>();
        log.Error("Unexpected Error", t.Exception);

    }, TaskContinuationOptions.OnlyOnFaulted);
}
11
Filimindji

Je suppose que la question se pose, pourquoi auriez-vous besoin de faire cela? La raison de async en C # 5.0 est donc vous pouvez attendre un résultat. Cette méthode n'est pas réellement asynchrone, mais simplement appelée à la fois afin de ne pas trop interférer avec le thread actuel.

Peut-être serait-il préférable de commencer un fil et de le laisser se terminer tout seul.

2
rhughes

La solution consiste à démarrer HttpClient dans une autre tâche d'exécution sans contexte de synchronisation:

var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);
1
Ernesto Garcia

Cela s'appelle feu et oublie, et il y a une extension pour ça.

Consomme une tâche et ne fait rien avec elle. Utile pour les appels automatiques de méthodes asynchrones au sein de méthodes asynchrones.

Intall paquet de nuget .

Utilisation:

MyAsyncMethod().Forget();
1
wast

Sur les technologies avec des boucles de messages (vous ne pouvez pas savoir si ASP en est une), vous pouvez bloquer la boucle et traiter les messages jusqu'à la fin de la tâche, et utiliser ContinueWith pour débloquer le code:

public void WaitForTask(Task task)
{
    DispatcherFrame frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false));
    Dispatcher.PushFrame(frame);
}

Cette approche est similaire au blocage sur ShowDialog tout en maintenant la réactivité de l'interface utilisateur.

0
haimb