Saltar al contenido

Consejos avanzados para el uso de Task.Run con Async/Await

Retomemos donde lo dejamos con la aplicación de la guía anterior. Esa aplicación descargó una imagen y luego la difuminó usando una librería llamada ImageSharp (disponible en NuGet como SixLabors.ImageSharp). Definimos un método llamado BlurImage de la siguiente manera:

12345678910111213staticasync Task<byte[]>BlurImage(string imagePath){returnawait Task.Run(()=>{var image = Image.Load(imagePath); image. Mutar(ctx => ctx.GaussianBlur());using(var memoryStream =newMemoryStream()){ image.SaveAsJpeg(memoryStream);return memoryStream.ToArray();}});}

csharp

Consejos avanzados para el uso de Task.Run con Async/Await
Consejos avanzados para el uso de Task.Run con Async/Await

Fíjense que la llamada a Task.Run es inmediatamente antes del código de procesamiento de la imagen. ¿Es ese el mejor enfoque?

Ya sea por conveniencia o claridad, puede que te encuentres haciendo una llamada a Task.Run tan cerca como sea posible del código intensivo de la CPU, muy parecido al método anterior. Sin embargo, a medida que su aplicación aumenta en complejidad, esto resulta ser subóptimo. Para ilustrar esto, imagina si en el futuro quisiéramos añadir un método a nuestra aplicación que rotara, se oscureciera, y se desdibujara. Podríamos empezar escribiendo algo como lo siguiente:

123456789estaticasyncTaskProcessImage(byte[] imageData){await Task.Run(()=>{RotateImage(imageData);DarkenImage(imageData);BlurImage(imageData);}}

csharp

Pero entonces notamos que BlurImage (o una versión de él que acepta una matriz de bytes) ya devuelve una Tarea, así que la cambiamos a:

123456await Task.Run(async()=await Task.Run(async()=RotateImage(imageData);DarkenImage(imageData);awaitBlurImage(imageData);}

csharp

Y entonces notamos que el propio BlurImage llama a Task.Run, lo que significa que ahora tenemos una llamada anidada Task.Run. Así que estaríamos lanzando un hilo desde dentro de otro hilo. Esto, de nuevo, no es el mejor uso de los recursos del sistema, y probablemente tendrá un impacto negativo en el rendimiento. Por eso se desaconseja a los autores de bibliotecas que utilicen los métodos de Task.Run in library: Debería depender del llamador cuando se lanzan los hilos.

Por lo tanto, se recomienda generalmente que las llamadas a Task.Run se realicen lo más cerca posible del código de la interfaz de usuario y de los manejadores de eventos. Siguiendo esta recomendación, encontrarás que la mayoría del código de la CPU termina siendo escrito como síncrono, y Task.Run va en el método de llamada más externo. Así que, en este ejemplo, terminaríamos con algo como:

12345678910111213estaticasyevitarOnButtonClick(){byte[] imageData =awaitLoadImage();await Task. Run(()=ProcessImage(ref imageData));awaitSaveImage(imageData);}staticvoidProcessImage(refbyte[] imageData){RotateImage(ref imageData);DarkenImage(ref imageData);BlurImage(ref imageData);}

csharp

…y BlurImage sería simplemente:

12345678910estáticovoyBlurImage(refbyte[] imagenDatos){var imagen = Imagen.Cargar(imagenDatos); imagen.Mutar(ctx => ctx.GaussianBlur());using(var memoriaStream =nuevoMemoriaStream()){ imagen.GuardarComoJpeg(memoriaStream); imagenDatos = memoriaStream.ToArray();}}

csharp