¿Por qué debería crear operaciones WebAPI asincrónicas en lugar de sincronizarlas?


Tengo la siguiente operación en una API web que he creado:

// 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);
}

La llamada a este servicio web se realiza a través de una llamada Jquery Ajax de esta manera:

$.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);
      }          
  });

He visto algunos desarrolladores que implementan la operación anterior de esta manera:

// 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));
}

Tengo que decir, sin embargo, que GetProductsWithHistory() es una operación bastante larga. Dado mi problema y contexto, ¿cómo me beneficiará hacer que la operación WebAPI sea asíncrona?

Author: svick, 2014-10-02

2 answers

En su ejemplo específico, la operación no es asíncrona en absoluto, por lo que lo que está haciendo es asíncrona sobre sincronización. Solo estás liberando un hilo y bloqueando otro. No hay razón para eso, porque todos los subprocesos son subprocesos de grupo de subprocesos (a diferencia de una aplicación GUI).

En mi discusión de "async sobre sync", sugerí fuertemente que si tiene una API que se implementa internamente de forma sincrónica, no debe exponer una contraparte asincrónica que simplemente envuelve la método síncrono en Task.Run.

Desde ¿Debo exponer envoltorios síncronos para métodos asíncronos?

Sin embargo, al hacer llamadas WebAPI async donde hay una operación asíncrona real (generalmente E/S) en lugar de bloquear un subproceso que se sienta y espera un resultado, el subproceso vuelve al grupo de subprocesos y, por lo tanto, puede realizar alguna otra operación. Sobre todo, eso significa que su aplicación puede hacer más con menos recursos y eso mejora la escalabilidad.

 92
Author: i3arnon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-16 13:35:36

Un enfoque podría ser (he utilizado esto con éxito en aplicaciones de clientes) tener un Servicio de Windows ejecutando las operaciones largas con hilos de trabajo, y luego hacer esto en IIS para liberar los hilos hasta que se complete la operación de bloqueo: Tenga en cuenta que esto supone que los resultados se almacenan en una tabla (filas identificadas por jobId) y un proceso más limpio que los limpia algunas horas después de su uso.

Para responder a la pregunta, "Dado mi problema y contexto, ¿cómo va a hacer la operación WebAPI ¿me beneficiaría asíncrono?"dado que es" una operación bastante larga", estoy pensando en muchos segundos en lugar de ms, este enfoque libera hilos de IIS. Obviamente, también tiene que ejecutar un servicio de Windows que a su vez toma recursos, pero este enfoque podría evitar que una inundación de las consultas lentas robe hilos de otras partes del sistema.

// 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
Author: Jeff Davies,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-21 10:23:12