77 votes

Utiliser async/await ou task dans un contrôleur d'api web (.net core)

J'ai une API .net core qui possède un contrôleur qui construit un objet agrégé à retourner. L'objet qu'il crée est composé de données provenant de 3 appels de méthode à une classe de service. Ces méthodes sont toutes indépendantes les unes des autres et peuvent être exécutées indépendamment les unes des autres. Actuellement, j'utilise des tâches pour améliorer les performances de ce contrôleur. La version actuelle ressemble à quelque chose comme ceci...

[HttpGet]
public IActionResult myControllerAction()
{      
    var data1 = new sometype1();
    var data2 = new sometype2();
    var data3 = new List<sometype3>();

    var t1 = new Task(() => { data1 = service.getdata1(); });
    t1.Start();

    var t2 = new Task(() => { data2 = service.getdata2(); });
    t2.Start();

    var t3 = new Task(() => { data3 = service.getdata2(); });
    t3.Start();

    Task.WaitAll(t1, t2, t3);

    var data = new returnObject
    {
         d1 = data1,
         d2 = data2,
         d2 = data3
    };

    return Ok(data);
}

Cela fonctionne bien, mais je me demande si l'utilisation de tâches est la meilleure solution ici ? L'utilisation d'async/await serait-elle une meilleure idée et un moyen plus accepté ?

Par exemple, le contrôleur doit-il être marqué comme asynchrone et un await doit-il être placé sur chaque appel aux méthodes du service ?

1 votes

Fait service ne propose pas d'alternatives asynchrones pour le getdata méthodes ? Par ailleurs, il faut généralement préférer Task.Run() plutôt que de créer des tâches et de les lancer manuellement.

2 votes

Async/await n'aide pas le parallélisme dans ce sens. En fait, il sérialise vos 3 demandes de service. En revanche, il peut vous aider lorsque vous faites Task.WaitAll puisque vous parquez un fil avec cette phrase.

1 votes

Il n'y a aucune raison de créer des tâches à froid puis d'appeler. Start sur eux. Ce ne sont pas des fils. Utilisez simplement Task.Run . Ensuite, utilisez await Task.WhenAll

133voto

Stephen Cleary Points 91731

Cela fonctionne bien, mais je me demande si l'utilisation de tâches est la meilleure solution ici ? L'utilisation d'async/await serait-elle une meilleure idée et un moyen plus accepté ?

Oui, absolument. Le traitement parallèle sur ASP.NET consomme plusieurs threads par requête, ce qui peut avoir un impact important sur votre évolutivité. Le traitement asynchrone est de loin supérieur pour les E/S.

Pour utiliser async Pour cela, commencez par l'appel le plus bas, quelque part dans votre service. Il effectue probablement un appel HTTP à un moment donné ; modifiez-le pour utiliser des appels HTTP asynchrones (par exemple, HttpClient ). Soit async se développent naturellement à partir de là.

Finalement, vous finirez par avoir un système asynchrone. getdata1Async , getdata2Async et getdata3Async qui peuvent être consommées simultanément en tant que telles :

[HttpGet]
public async Task<IActionResult> myControllerAction()
{
  var t1 = service.getdata1Async();
  var t2 = service.getdata2Async();
  var t3 = service.getdata3Async();
  await Task.WhenAll(t1, t2, t3);

  var data = new returnObject
  {
    d1 = await t1,
    d2 = await t2,
    d3 = await t3
  };

  return Ok(data);
}

Avec cette approche, pendant que les trois appels de service sont en cours, myControllerAction utilise zéro au lieu de quatre .

4 votes

N'est-il pas redondant de faire await Task.WhenAll() y attendre pour chaque tâche dans le returnObject ? Je ne vois pas pourquoi il ne serait pas suffisant de faire l'un ou l'autre. Pouvez-vous m'expliquer :)

9 votes

Si les types de retour sont différents, vous devez await chaque tâche. Je préfère Task.WhenAll même dans ce cas, car cela rend le code plus clair. C'est aussi un peu plus efficace (on ne reprend le contexte qu'une fois au lieu de 3), mais ma raison principale est la clarté du code.

0 votes

@StephenCleary Comment utiliser zéro thread si mon API doit appeler System.Diagnostics.Process avec WaitForExit ? J'ai l'intention d'implémenter stackoverflow.com/a/10789196/241004 comme solution de rechange. Avez-vous une meilleure idée ?

17voto

Sergey Berezovskiy Points 102044
[HttpGet]
public async Task<IActionResult> GetAsync()
{      
    var t1 = Task.Run(() => service.getdata1());
    var t2 = Task.Run(() => service.getdata2());
    var t3 = Task.Run(() => service.getdata3());

    await Task.WhenAll(t1, t2, t3);

    var data = new returnObject
    {
        d1 = t1.Status == TaskStatus.RanToCompletion ? t1.Result : null,
        d2 = t2.Status == TaskStatus.RanToCompletion ? t2.Result : null,
        d3 = t3.Status == TaskStatus.RanToCompletion ? t3.Result : null
    };

   return Ok(data);
}
  1. Votre fil d'action est actuellement bloqué lorsque vous attendez des tâches. Utilisez TaskWhenAll pour renvoyer un objet Task attendu. Ainsi, avec la méthode asynchrone, vous pouvez attendre des tâches au lieu de bloquer un fil.
  2. Au lieu de créer des variables locales et de les assigner dans les tâches, vous pouvez utiliser la fonction Task<T> pour renvoyer les résultats du type requis.
  3. Au lieu de créer et d'exécuter des tâches, utilisez Task<TResult>.Run méthode
  4. Je recommande d'utiliser une convention pour les noms d'action - si l'action accepte une requête GET, son nom doit commencer avec Get
  5. Ensuite, vous devez vérifier si les tâches se sont bien déroulées. Cela se fait en vérifiant le statut de la tâche. Dans mon exemple, j'ai utilisé null des valeurs pour les propriétés de l'objet de retour si certaines des tâches ne se terminent pas avec succès. Vous pouvez utiliser une autre approche - par exemple, renvoyer une erreur si certaines tâches ont échoué.

5 votes

Je pense que vous devez expliquer le contrôle pour RanToCompletion . Prévoyez-vous une annulation anticipée ? Est-ce que l'un des appels a été défectueux ? await Task.WhenAll soulèverait une exception

0 votes

Êtes-vous sûr que vous pouvez simplement renommer la méthode get dans WebApi ? En MVC, vous ne pouvez pas le faire sans modifier le comportement.

0 votes

@Sefe yep, vous avez raison. Je n'avais pas remarqué qu'il s'agissait de mvc core, je vais rétablir certains changements liés à la web-api dans un moment.

3voto

Thomas D. Points 471

Si je comprends bien, vous voulez que cela s'exécute en parallèle, donc je ne pense pas qu'il y ait un problème avec votre code. Comme Gabriel l'a mentionné, vous pourriez attendre la fin des tâches.

[HttpGet]
public async Task<IActionResult> myControllerAction()
{      
  var data1 = new sometype1();
  var data2 = new sometype2();
  var data3 = new List<sometype3>();

  var t1 = Task.Run(() => { data1 = service.getdata1(); });
  var t2 = Task.Run(() => { data2 = service.getdata2(); });
  var t3 = Task.Run(() => { data3 = service.getdata3(); });

  await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here

  var data = new returnObject
  {
      d1 = data1,
      d2 = data2,
      d2 = data3
  };

 return Ok(data);
}

Vous pourriez également utiliser les résultats des tâches pour économiser quelques lignes de code et rendre le code globalement "meilleur" (voir commentaires) :

[HttpGet]
public async Task<IActionResult> myControllerAction()
{      
  var t1 = Task.Run(() => service.getdata1() );
  var t2 = Task.Run(() => service.getdata2() );
  var t3 = Task.Run(() => service.getdata3() );

  await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here

  var data = new returnObject
  {
      d1 = t1.Result,
      d2 = t2.Result,
      d2 = t3.Result
  };

 return Ok(data);
}

1 votes

Le premier extrait contient plusieurs problèmes - initialisation des variables avec des objets jetables, puis capturer ces variables.

0 votes

Je reconnais que ce n'est pas optimal, c'est pourquoi j'ai fourni une "meilleure" solution avec mon deuxième extrait de code. Quoi qu'il en soit, je ne pense pas que le premier extrait de code pose de réels problèmes. Le compilateur transformera les variables locales à jeter en champs privés, ce qui devrait fonctionner parfaitement. (Encore une fois, je sais que ce n'est pas idéal).

0 votes

Au moins, vous devriez utiliser Task pour le type de méthode en raison de l'utilisation asynchrone.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X