¿Task.Run se considera una mala práctica en una aplicación web ASP .NET MVC?

Fondo

Actualmente estamos desarrollando una aplicación web, que se basa en ASP .NET MVC 5, Angular.JS 1.4, Web API 2 y Entity Framework 6. Por razones de escalabilidad, la aplicación web heavility se basa en el patrón async / await. Nuestro dominio requiere algunos cálculos intensivos de CPU, lo que puede llevar algunos segundos ( Ver la publicación de blog de Stephen Cleary ), usaron ConfigureAwait (falso).

Ejemplo

public async Task CalculateAsync(double param1, double param2) { // CalculateSync is synchronous and cpu-intensive ( this.CalculateSync(param1, param2))).ConfigureAwait(false); } 

Preguntas

  • ¿Hay algún beneficio de rendimiento en el uso de Task.Run en un controlador de API web asíncrono para operaciones vinculadas a la CPU?
  • ¿ConfigureAwait (falso) realmente evita la creación de un hilo adicional?

¿Hay algún beneficio de rendimiento en el uso de Task.Run en un controlador de API web asíncrono para operaciones vinculadas a la CPU?

Cero. Ninguna. De hecho, estás obstaculizando el rendimiento al generar un nuevo hilo. Dentro del contexto de una aplicación web, generar un hilo no es lo mismo que ejecutar en el “fondo”. Esto se debe a la naturaleza de una solicitud web. Cuando hay una solicitud entrante, se toma un hilo del grupo para atender la solicitud. El uso de async permite que el hilo sea devuelto antes del final de la solicitud, si y solo si el hilo está en un estado de espera, es decir, inactivo. Al generar un hilo para trabajar, se inactiva de manera efectiva el hilo primario, lo que permite que se devuelva al grupo, pero aún tiene un hilo activo. Devolver el hilo original al grupo no hace nada en ese punto. Luego, cuando el nuevo hilo termine su trabajo, debe solicitar un hilo principal del grupo y finalmente devolver la respuesta. La respuesta no se puede devolver hasta que se haya completado todo el trabajo, por lo tanto, si usa 1 hilo o cien, asincrónico o sincronizado, la respuesta no se puede devolver hasta que todo termine. Por lo tanto, usar hilos adicionales no hace más que agregar sobrecarga.

¿ConfigureAwait (falso) realmente evita la creación de un hilo adicional?

No, o más apropiadamente, no se trata de eso. ConfigureAwait es solo una sugerencia de optimización, y solo determina si el contexto original se mantiene entre saltos de hilo. Largo y corto, no tiene nada que ver con la creación de un hilo, y al menos en el contexto de una aplicación ASP.NET, tiene un impacto de rendimiento insignificante en ambos sentidos.

¿Hay algún beneficio de rendimiento en el uso de Task.Run en un controlador de API web asíncrono para operaciones vinculadas a la CPU?

No. Y no importa si está vinculado a la CPU o no.

Task.Run descarga el trabajo a un hilo de ThreadPool . La solicitud de API web ya utiliza un hilo de ThreadPool por lo que solo está limitando la escalabilidad mediante la descarga a otro hilo sin ningún motivo.

Esto es útil en aplicaciones de interfaz de usuario, donde el hilo de la interfaz de usuario es un hilo único especial.

¿ConfigureAwait (falso) realmente evita la creación de un hilo adicional?

No afecta la creación de subprocesos de una forma u otra. Todo lo que hace es configurar si continuar en el SynchronizationContext capturado o no.

¿Hay algún beneficio de rendimiento en el uso de Task.Run en un controlador de API web asíncrono para operaciones vinculadas a la CPU?

Piensa en lo que realmente sucede: Task.Run() crea una Tarea en el grupo de subprocesos, y tu operador en await liberará el subproceso (supongo que todos los métodos de la stack también están esperando). ¡Ahora su hilo ha vuelto al grupo y podría retomar esa misma tarea! En este escenario, obviamente no hay ganancia de rendimiento. Hay un golpe de rendimiento, en realidad. Pero si el hilo recoge otra Tarea (eso es probablemente lo que sucederá), otro hilo tendría que recoger CalculateSync() Tarea y continuar desde donde el anterior se detuvo. Tendría más sentido permitir que el subproceso original ejecute CalculateSync() en primer lugar, sin Tareas involucradas, y deje que el otro subproceso tenga las otras Tareas en cola.

¿ConfigureAwait (falso) realmente evita la creación de un hilo adicional?

De ningún modo. Simplemente señala que la continuación no se debe ejecutar en el contexto de la persona que llama.

Hay una cosa más que debes tener en cuenta. Cuando le dijiste que tu API está haciendo una tarea intensiva de CPU, entonces asincrónica / esperando ayuda para ejecutar el proceso en otro hilo y liberar tu grupo de aplicaciones para otra solicitud. Significa que su API web puede manejar más solicitudes por segundo si utiliza async / await correctamente.

En su caso, tenga este aspecto. this.CalculateSync(param1, param2) no es un método asíncrono, por lo tanto, para llamarlo de forma asincrónica, debe utilizar Task.Run.

Recomiendo eliminar .ConfigureAwait(false) ya que esto realmente reducirá su rendimiento.