web-dev-qa-db-fra.com

Utilisation asynchrone / wait ou de la tâche dans le contrôleur de l'API Web (noyau .net)

J'ai une API principale .net qui a un contrôleur qui construit un objet agrégé à renvoyer. l'objet qu'il crée est constitué de données provenant de 3 appels de méthode à une classe de service. Celles-ci sont toutes indépendantes les unes des autres et peuvent être exécutées isolément 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 ça ...

[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 asynchrone/wait serait-elle une meilleure idée et une méthode plus acceptée?

Par exemple, le contrôleur doit-il être marqué comme asynchrone et une attente attendue à chaque appel des méthodes de service?

39
Ben Cameron

Cela fonctionne bien, mais je me demande si l’utilisation de tâches est la meilleure solution ici? L'utilisation asynchrone/wait serait-elle une meilleure idée et une méthode plus acceptée?

Oui absolument. Le traitement parallèle sur ASP.NET consomme plusieurs threads par demande, 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, commencez par votre appel le plus bas, quelque part dans votre service. Il s'agit probablement d'un appel HTTP à un moment donné; changez-la pour utiliser des appels HTTP asynchrones (par exemple, HttpClient). Puis laissez async pousser naturellement à partir de là.

Vous finirez par vous retrouver avec les méthodes asynchrones 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, alors que les trois appels de service sont en cours, myControllerAction utilise zéro threads au lieu de quatre.

60
Stephen Cleary
[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 thread d'action est actuellement bloqué lorsque vous attendez des tâches. Utilisez TaskWhenAll pour renvoyer un objet Task en attente. Ainsi, avec la méthode asynchrone, vous pouvez attendre des tâches au lieu de bloquer le thread.
  2. Au lieu de créer des variables locales et de les affecter à des tâches, vous pouvez utiliser Task<T> pour renvoyer des résultats du type requis.
  3. Au lieu de créer et d’exécuter des tâches, utilisez la méthode Task<TResult>.Run
  4. Je recommande d'utiliser la convention pour les noms d'action - si l'action accepte la requête GET, son nom doit commencer par Get
  5. Ensuite, vous devez vérifier si les tâches ont abouti. Cela se fait en vérifiant le statut de la tâche. Dans mon exemple, j'ai utilisé les valeurs null pour renvoyer les propriétés de l'objet si certaines tâches ne se terminent pas correctement. Vous pouvez utiliser une autre approche - par exemple renvoyer une erreur si certaines tâches ont échoué.
12
Sergey Berezovskiy

Si j'ai bien compris, vous voulez que cela s'exécute en parallèle, donc je ne pense pas qu'il y a un problème avec votre code. Comme Gabriel l'a mentionné, vous pouvez 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 pouvez également utiliser les résultats des tâches pour enregistrer certaines lignes de codes et améliorer globalement le code (voir les 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
Thomas D.