web-dev-qa-db-fra.com

Pourquoi devrais-je créer des opérations Web API asynchrones au lieu de synchroniser?

J'ai l'opération suivante dans une API Web que j'ai créée:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public CartTotalsDTO GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh);
}

L'appel à ce service Web est effectué via un appel Jquery Ajax de la manière suivante:

$.ajax({
      url: "/api/products/pharmacies/<%# Farmacia.PrimaryKeyId.Value.ToString() %>/page/" + vm.currentPage() + "/" + filter,
      type: "GET",
      dataType: "json",
      success: function (result) {
          vm.items([]);
          var data = result.Products;
          vm.totalUnits(result.TotalUnits);
      }          
  });

J'ai vu des développeurs implémenter l'opération précédente de cette façon:

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
    return await Task.Factory.StartNew(() => delegateHelper.GetProductsWithHistory(CustomerContext.Current.GetContactById(pharmacyId), refresh));
}

Il faut dire, cependant, que GetProductsWithHistory () est une opération assez longue. Étant donné mon problème et mon contexte, en quoi le fonctionnement asynchrone de WebAPI me sera-t-il utile?

108

Dans votre exemple spécifique, l'opération n'est pas du tout asynchrone, vous effectuez donc une synchronisation asynchrone. Vous ne faites que libérer un fil et en bloquer un autre. Il n'y a aucune raison à cela, car tous les threads sont des threads de pool de threads (contrairement à une application graphique).

Dans ma discussion sur "async over sync", j'ai fortement suggéré que, si vous avez une API implémentée de manière synchrone en interne, vous ne devez pas exposer une contrepartie asynchrone qui enveloppe simplement la méthode synchrone dans Task.Run.

De Dois-je exposer les wrappers synchrones pour les méthodes asynchrones?

Toutefois, lors d'appels WebAPI, async comportant une opération asynchrone réelle (généralement des E/S) au lieu de bloquer un thread qui attend et attend un résultat, le thread retourne dans le pool de threads et peut donc effectuer une autre opération. . Globalement, cela signifie que votre application peut faire plus avec moins de ressources, ce qui améliore l'évolutivité.

95
i3arnon

Une solution pourrait consister (j’ai utilisé cela avec succès dans des applications client) d’avoir un service Windows exécutant les longues opérations avec des threads de travail, puis de le faire dans IIS pour libérer les threads jusqu’à ce que le blocage soit effectué. complete: Notez que cela suppose que les résultats sont stockés dans un tableau (lignes identifiées par jobId) et qu'un processus de nettoyage les nettoie quelques heures après leur utilisation.

Pour répondre à la question "Compte tenu de mon problème et de mon contexte, en quoi le fonctionnement asynchrone de WebAPI me sera-t-il utile?" Etant donné qu'il s'agit d'une "opération assez longue", je réfléchis beaucoup de secondes plutôt que de ms, cette approche libère les threads IIS. De toute évidence, vous devez également exécuter un service Windows qui prend lui-même des ressources, mais cette approche pourrait empêcher un flot de requêtes lentes de voler des threads d'autres parties du système.

// GET api/<controller>
[HttpGet]
[Route("pharmacies/{pharmacyId}/page/{page}/{filter?}")]
public async Task<CartTotalsDTO> GetProductsWithHistory(Guid pharmacyId, int page, string filter = null ,[FromUri] bool refresh = false)
{
        var jobID = Guid.NewGuid().ToString()
        var job = new Job
        {
            Id = jobId,
            jobType = "GetProductsWithHistory",
            pharmacyId = pharmacyId,
            page = page,
            filter = filter,
            Created = DateTime.UtcNow,
            Started = null,
            Finished = null,
            User =  {{extract user id in the normal way}}
        };
        jobService.CreateJob(job);

        var timeout = 10*60*1000; //10 minutes
        Stopwatch sw = new Stopwatch();
        sw.Start();
        bool responseReceived = false;
        do
        {
            //wait for the windows service to process the job and build the results in the results table
            if (jobService.GetJob(jobId).Finished == null)
            {
                if (sw.ElapsedMilliseconds > timeout ) throw new TimeoutException();
                await Task.Delay(2000);
            }
            else
            {
                responseReceived = true;
            }
        } while (responseReceived == false);

    //this fetches the results from the temporary results table
    return jobService.GetProductsWithHistory(jobId);
}
0
user5292841