none
Debería usar siempre Async Await en Web Api Core? RRS feed

  • Pregunta

  • Hola

    He visto que muchas personas al momento de crear Apis en .Net Core. Le agregan la palabra clave "Async", pero exactamente cual es el beneficio de hacerlas asincronas si de igual forma siempre tienen que esperar una respuesta para retornar?

    Existe algun caso especial donde se deba usar o es recomendable usarlas siempre, etc?

    Cuales son sus ventajas, desventajas, etc???

    Saludos
    jueves, 13 de diciembre de 2018 18:16

Respuestas

  • Te doy la principal razón de usar programación asíncrona en ASP.NET (sea Core o no): Si las funciones de tus APIs o páginas web dependen de algún otro recurso que no sea la propia CPU (por ejemplo, bases de datos, archivos en disco, servicios web, etc.), entonces el mismo sitio web puede escalar hasta atender 100 veces más usuarios con llamadas asíncronas que con llamadas síncronas.

    Explico por qué:

    Cuando llega una petición al servidor (a un método de acción o una API), se le asigna un hilo para atenderla, y el hilo va ejecutando todo el código hasta que al final sirve la respuesta al llamante y se libera el hilo. Si mientras ese hilo se está ejecutando llega otra petición, se asigna un nuevo hilo, que ejecuta en paralelo la segunda petición. Y si siguen llegando más peticiones, se siguen abriendo más hilos hasta que se alcanza cierto límite máximo, que creo recordar que era de 25 hilos multiplicado por el número de CPUs.

    ¿Qué pasa si al llegar al máximo llegan más peticiones? Pues que se van poniendo en cola. ¿Hasta cuando? Hasta que la cola alcanza cierto límite, que creo que es de algo así como 1024 peticiones. Si después de eso llegan más peticiones, se devuelve un error que creo que es "503 Sever too busy" ("servidor demasiado ocupado").

    Incluso aunque no alcances este límite, se puede perjudicar el tiempo de respuesta de otras peticiones. Por ejemplo, podría haber usuarios que está haciendo simples navegaciones a páginas que son puro html y no requieren ningún procesamiento, y sin embargo se tarda mucho en responderles porque todos los hilos disponibles están ocupados atendiendo peticiones que son lentas de procesar.

    Y lo más curioso es que esta circunstancia de "estar demasiado ocupado" y no responder a tiempo puede ocurrir cuando la CPU está próxima al 0% de ocupación. ¿Por qué? Pues porque todos los hilos están "parados" esperando a que les responda un recurso externo, tal como una base de datos que está en otro servidor.

    ¿Cómo se resuelve? Con programación asíncrona, que era precisamente el tema inicial de la pregunta. Se hacen asíncronas las llamadas al recurso externo, y se marca como asíncrona la API que realiza esas llamadas. De esta manera, cuando la ejecución llega a la llamada al recurso externo, se libera el hilo, que puede pasar a atender la siguiente llamada que esté en cola, y de esa manera no se retrasa la atención a dichas llamadas. Mientras tanto, esa operación que está esperando al recurso externo se suspende, y cuando por fin responde el recurso externo, continúa ejecutándose donde se interrumpió (poniéndose antes en cola si es que todos los hilos continúan ocupados). Así no se desperdicia tiempo con la CPU al 0% esperando a recursos externos.

    jueves, 13 de diciembre de 2018 21:38
  • En cada una de las capas deben ser los métodos async?

    Efectivamente, a la vista de la explicación anterior se infiere que para poder reutilizar los hilos de ejecución y por tanto maximizar las peticiones que se atienden, se necesita que la llamada asíncrona se propague hasta el sitio donde se esté haciendo la llamada al recurso externo.

    Por lo tanto, si el recurso externo es una base de datos, hay que meter async/await en todos los pasos intermedios que haya desde el controlador hasta la capa en la que se hacen las llamadas a la base de datos (que también deben ser asíncronas).

    viernes, 14 de diciembre de 2018 20:17
  • Si el método al que vas a llamar no es asíncrono, puedes crear una nueva tarea, por ejemplo con Task.Run(...) si quieres que sea awaitable, pero si de todas maneras no vas a esperar el resultado, entonces no hace falta que recurras a async/await, basta con que lances un hilo vulgar y corriente, por ejemplo:

    Thread.Start(new ThreadStart(()=>log.WriteLog("Test")));

    Ojo, si haces estas cosas, asegúrate de que tu método WriteLog está diseñado de manera que sea thread-safe.

    lunes, 24 de diciembre de 2018 18:22

Todas las respuestas

  • Te doy la principal razón de usar programación asíncrona en ASP.NET (sea Core o no): Si las funciones de tus APIs o páginas web dependen de algún otro recurso que no sea la propia CPU (por ejemplo, bases de datos, archivos en disco, servicios web, etc.), entonces el mismo sitio web puede escalar hasta atender 100 veces más usuarios con llamadas asíncronas que con llamadas síncronas.

    Explico por qué:

    Cuando llega una petición al servidor (a un método de acción o una API), se le asigna un hilo para atenderla, y el hilo va ejecutando todo el código hasta que al final sirve la respuesta al llamante y se libera el hilo. Si mientras ese hilo se está ejecutando llega otra petición, se asigna un nuevo hilo, que ejecuta en paralelo la segunda petición. Y si siguen llegando más peticiones, se siguen abriendo más hilos hasta que se alcanza cierto límite máximo, que creo recordar que era de 25 hilos multiplicado por el número de CPUs.

    ¿Qué pasa si al llegar al máximo llegan más peticiones? Pues que se van poniendo en cola. ¿Hasta cuando? Hasta que la cola alcanza cierto límite, que creo que es de algo así como 1024 peticiones. Si después de eso llegan más peticiones, se devuelve un error que creo que es "503 Sever too busy" ("servidor demasiado ocupado").

    Incluso aunque no alcances este límite, se puede perjudicar el tiempo de respuesta de otras peticiones. Por ejemplo, podría haber usuarios que está haciendo simples navegaciones a páginas que son puro html y no requieren ningún procesamiento, y sin embargo se tarda mucho en responderles porque todos los hilos disponibles están ocupados atendiendo peticiones que son lentas de procesar.

    Y lo más curioso es que esta circunstancia de "estar demasiado ocupado" y no responder a tiempo puede ocurrir cuando la CPU está próxima al 0% de ocupación. ¿Por qué? Pues porque todos los hilos están "parados" esperando a que les responda un recurso externo, tal como una base de datos que está en otro servidor.

    ¿Cómo se resuelve? Con programación asíncrona, que era precisamente el tema inicial de la pregunta. Se hacen asíncronas las llamadas al recurso externo, y se marca como asíncrona la API que realiza esas llamadas. De esta manera, cuando la ejecución llega a la llamada al recurso externo, se libera el hilo, que puede pasar a atender la siguiente llamada que esté en cola, y de esa manera no se retrasa la atención a dichas llamadas. Mientras tanto, esa operación que está esperando al recurso externo se suspende, y cuando por fin responde el recurso externo, continúa ejecutándose donde se interrumpió (poniéndose antes en cola si es que todos los hilos continúan ocupados). Así no se desperdicia tiempo con la CPU al 0% esperando a recursos externos.

    jueves, 13 de diciembre de 2018 21:38
  • Te doy la principal razón de usar programación asíncrona en ASP.NET (sea Core o no): Si las funciones de tus APIs o páginas web dependen de algún otro recurso que no sea la propia CPU (por ejemplo, bases de datos, archivos en disco, servicios web, etc.), entonces el mismo sitio web puede escalar hasta atender 100 veces más usuarios con llamadas asíncronas que con llamadas síncronas.

    Explico por qué:

    Cuando llega una petición al servidor (a un método de acción o una API), se le asigna un hilo para atenderla, y el hilo va ejecutando todo el código hasta que al final sirve la respuesta al llamante y se libera el hilo. Si mientras ese hilo se está ejecutando llega otra petición, se asigna un nuevo hilo, que ejecuta en paralelo la segunda petición. Y si siguen llegando más peticiones, se siguen abriendo más hilos hasta que se alcanza cierto límite máximo, que creo recordar que era de 25 hilos multiplicado por el número de CPUs.

    ¿Qué pasa si al llegar al máximo llegan más peticiones? Pues que se van poniendo en cola. ¿Hasta cuando? Hasta que la cola alcanza cierto límite, que creo que es de algo así como 1024 peticiones. Si después de eso llegan más peticiones, se devuelve un error que creo que es "503 Sever too busy" ("servidor demasiado ocupado").

    Incluso aunque no alcances este límite, se puede perjudicar el tiempo de respuesta de otras peticiones. Por ejemplo, podría haber usuarios que está haciendo simples navegaciones a páginas que son puro html y no requieren ningún procesamiento, y sin embargo se tarda mucho en responderles porque todos los hilos disponibles están ocupados atendiendo peticiones que son lentas de procesar.

    Y lo más curioso es que esta circunstancia de "estar demasiado ocupado" y no responder a tiempo puede ocurrir cuando la CPU está próxima al 0% de ocupación. ¿Por qué? Pues porque todos los hilos están "parados" esperando a que les responda un recurso externo, tal como una base de datos que está en otro servidor.

    ¿Cómo se resuelve? Con programación asíncrona, que era precisamente el tema inicial de la pregunta. Se hacen asíncronas las llamadas al recurso externo, y se marca como asíncrona la API que realiza esas llamadas. De esta manera, cuando la ejecución llega a la llamada al recurso externo, se libera el hilo, que puede pasar a atender la siguiente llamada que esté en cola, y de esa manera no se retrasa la atención a dichas llamadas. Mientras tanto, esa operación que está esperando al recurso externo se suspende, y cuando por fin responde el recurso externo, continúa ejecutándose donde se interrumpió (poniéndose antes en cola si es que todos los hilos continúan ocupados). Así no se desperdicia tiempo con la CPU al 0% esperando a recursos externos.

    Alberto muchas gracias, excelente explicación. Agradezco tu tiempo.

    Una última consulta, en un Web Api de 3 capas (estás apis por lo general se conectan a una base de datos o llaman a otras apis):

    • Web Api
    • Facade (opcional)
    • BL
    • DAL

    En cada una de las capas deben ser los métodos async?

    Saludos

    viernes, 14 de diciembre de 2018 19:21
  • hola

    >>estás apis por lo general se conectan a una base de datos o llaman a otras apis

    depende, si es una webapi que forma parte de una arquitectura de micro servicios quizas invoque a otra api

    pero si esta devuelve datos simples podria conectarse mediante la capa de acceso a datos a la db

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    viernes, 14 de diciembre de 2018 19:54
  • En cada una de las capas deben ser los métodos async?

    Efectivamente, a la vista de la explicación anterior se infiere que para poder reutilizar los hilos de ejecución y por tanto maximizar las peticiones que se atienden, se necesita que la llamada asíncrona se propague hasta el sitio donde se esté haciendo la llamada al recurso externo.

    Por lo tanto, si el recurso externo es una base de datos, hay que meter async/await en todos los pasos intermedios que haya desde el controlador hasta la capa en la que se hacen las llamadas a la base de datos (que también deben ser asíncronas).

    viernes, 14 de diciembre de 2018 20:17
  • En cada una de las capas deben ser los métodos async?

    Efectivamente, a la vista de la explicación anterior se infiere que para poder reutilizar los hilos de ejecución y por tanto maximizar las peticiones que se atienden, se necesita que la llamada asíncrona se propague hasta el sitio donde se esté haciendo la llamada al recurso externo.

    Por lo tanto, si el recurso externo es una base de datos, hay que meter async/await en todos los pasos intermedios que haya desde el controlador hasta la capa en la que se hacen las llamadas a la base de datos (que también deben ser asíncronas).

    Hola Alberto

    Tengo algunas dudas en cuanto a como debe ser el consumo de los métodos Asycn, no se aún del todo donde deben ir los await. De esta forma estaría bien (es un ejemplo de prueba basado en un app de 3 capas)? 
     public class TestDAL
        {
            public async Task<int> Insert()
            {
                DataTable dataTable = new DataTable();
                using (SqlConnection cnn = new SqlConnection("<sc>"))
                {
                    await cnn.OpenAsync();
                    using (SqlCommand command = new SqlCommand("<query>"))
                    {
                        object id = await command.ExecuteScalarAsync();
                        return (int)id;
                    }
                }
            }
        }
        public class TestBL
        {
            public async Task<int> Insert()
            {
                TestDAL dal = new TestDAL();
                int resultado = 0;
                try
                {
                    resultado = await dal.Insert();
                }
                catch (Exception)
                {
                    //Do sonething
                }
                return resultado;
            }
        }
        [ApiController]
        public class TestApi : ControllerBase
        {
            [HttpGet]
            [HttpGet]
            public async Task<int> IndexAsync()
            {
                TestBL bl = new TestBL();
                return await bl.Insert();
            }
        }
    Gracias
    • Editado AdyIr lunes, 24 de diciembre de 2018 16:53
    lunes, 24 de diciembre de 2018 14:51
  • Sí, tiene toda la pinta de estar bien tal como lo tienes. La regla sería que siempre que llames a un método async, pones await en la llamada. Y todos los métodos que dentro contengan una de estas llamadas, a su vez los declaras como async.
    lunes, 24 de diciembre de 2018 16:06
  • Sí, tiene toda la pinta de estar bien tal como lo tienes. La regla sería que siempre que llames a un método async, pones await en la llamada. Y todos los métodos que dentro contengan una de estas llamadas, a su vez los declaras como async.

    Gracias nuevamente, para cerrar la respuesta la última pregunta.

    Si dentro de mi método ASYNC, tengo un método que quiero ejecutar pero no me interesa esperar respuesta (un registro de log por ejemplo). Cual sería forma correcta de llamar a este método?

    public class TestBL
        {
            public async Task<int> Insert()
            {
                TestDAL dal = new TestDAL();
                int resultado = 0;
                try
                {
                    //Llamo mi método async el cual si debo esperar respuesta
                    resultado = await dal.Insert();
    
                    //Llamo mi método el cual no necesito respuesta ¿Como lo mando a ejecutar en 2do plano?
                    Log log = new Log();
                    log.WriteLog("Test");
                }
                catch (Exception)
                {
                    //Do sonething
                }
                return resultado;
            }
        }
        public class Log
        {
            public void WriteLog(string mensaje)
            {
               //Registra un log en base de datos
            }
        }

    Gracias una vez más....

    lunes, 24 de diciembre de 2018 16:52
  • Si el método al que vas a llamar no es asíncrono, puedes crear una nueva tarea, por ejemplo con Task.Run(...) si quieres que sea awaitable, pero si de todas maneras no vas a esperar el resultado, entonces no hace falta que recurras a async/await, basta con que lances un hilo vulgar y corriente, por ejemplo:

    Thread.Start(new ThreadStart(()=>log.WriteLog("Test")));

    Ojo, si haces estas cosas, asegúrate de que tu método WriteLog está diseñado de manera que sea thread-safe.

    lunes, 24 de diciembre de 2018 18:22