C# Espera Múltiples Tareas

Naila Saad Siddiqui 15 febrero 2024
  1. Programación Síncrona y Asíncrona en C#
  2. Implementar el método async y esperar tareas múltiples en C#
C# Espera Múltiples Tareas

Esta guía trivial analiza el modelo de programación asincrónica y explica las palabras clave relacionadas como async, await y Task. En particular, demuestra el concepto de esperar múltiples tareas usando C#.

Antes de pasar al tema real, debemos repasar brevemente las diferencias entre los modelos de programación síncrona y asíncrona. ¡Entonces empecemos!

Programación Síncrona y Asíncrona en C#

Los modelos sincrónicos y asincrónicos son cruciales en la programación de computadoras. Las terminologías proporcionan información sobre las funciones de cada modelo de programación y sus distinciones.

Las tareas sincrónicas generalmente se llevan a cabo en orden secuencial, una tras otra. Las tareas asincrónicas se pueden realizar simultáneamente (en cualquier orden).

Programación Síncrona en C#

Synchronous es una arquitectura de bloqueo que se adapta mejor a los sistemas de programación reactiva. Como modelo de un solo hilo, se adhiere a un conjunto rígido de secuencias, por lo que cada operación se lleva a cabo una a la vez y en una sucesión precisa.

Las instrucciones para otros procedimientos se bloquean mientras se lleva a cabo un proceso. La finalización de una tarea inicia la siguiente, y así sucesivamente.

Considere un ejemplo de walkie-talkie para comprender cómo funciona la programación síncrona.

Una persona habla durante una llamada telefónica mientras la otra escucha. La segunda persona suele responder inmediatamente después de que la primera haya terminado de hablar.

En términos de programación, consideremos el siguiente ejemplo para entender la programación síncrona:

using System;
using System.Threading;
public class Program {
  public static void Main(string[] args) {
    Process1();
    Process2();
  }
  static void Process1() {
    Console.WriteLine("Process 1 started");
    // You can write some code here that can take a longer time
    Thread.Sleep(5000);  // holing on execution for 5 sec
    Console.WriteLine("Process 1 Completed");
  }
  static void Process2() {
    Console.WriteLine("Process 2 Started");
    // write some code here that takes relatively less time
    Console.WriteLine("Process 2 Completed");
  }
}

El método Process1() en el ejemplo anterior se usa para realizar un proceso largo, como leer un archivo del servidor, contactar una API web que devuelve una gran cantidad de datos o cargar o descargar un archivo enorme.

Se tarda un poco más en ejecutarse (para fines ilustrativos, Thread.Sleep(5000) lo mantiene durante 5 segundos). El método process2() sigue al método Process1() y no se inicia simultáneamente.

El programa anterior se ejecuta sincrónicamente. Indica que la ejecución comienza con la función Main(), después de lo cual el método Process1() y el método Process2() se llevan a cabo en orden (es decir, estrictamente uno tras otro).

Una aplicación se detiene y deja de responder durante la ejecución (vea esto en la pantalla de salida a continuación). La programación síncrona está esperando para pasar a la línea siguiente hasta que la línea anterior haya terminado de ejecutarse.

Veamos la salida para comprender completamente la programación síncrona.

Salida de programación síncrona

Puede ver en el resultado anterior que la ejecución está bloqueada mientras espera que se complete el Proceso1. Tan pronto como se completa su ejecución, Process2 se ejecuta.

Programación Asíncrona en C#

Por otro lado, la programación asíncrona es un estilo de subprocesos múltiples que funciona mejor en redes y comunicaciones. Como un diseño sin bloqueo, la arquitectura asíncrona no impide la ejecución posterior mientras se ejecutan una o más operaciones.

Varias operaciones relacionadas pueden ejecutarse simultáneamente con la programación asíncrona sin esperar a que finalicen otras acciones. En lugar de responder inmediatamente después de recibir un mensaje, las personas involucradas en la comunicación asíncrona esperan hasta que sea conveniente o factible antes de leerlo y procesarlo.

Las técnicas de comunicación asincrónica incluyen mensajes de texto. Una persona puede enviar un mensaje de texto y el destinatario puede responder cuando lo desee. Mientras espera una respuesta, el remitente puede realizar otras acciones.

De acuerdo con el ejemplo discutido anteriormente, el método Process1(), por ejemplo, se ejecutará en un subproceso diferente del grupo de subprocesos en el modelo de programación asincrónica. Por el contrario, el subproceso principal de la aplicación continuará ejecutando la instrucción posterior.

Microsoft sugiere usar el patrón asíncrono basado en tareas al implementar la programación asíncrona en aplicaciones .NET Framework o .NET Core usando las palabras clave async y await y la clase Task o Task<TResult>.

Reescribamos el código anterior usando programación asíncrona:

using System;
using System.Threading.Tasks;

public class Program {
  public static async Task Main(string[] args) {
    Process1();
    Process2();
    Console.ReadKey();
  }

  public static async void Process1() {
    Console.WriteLine("Process 1 Started");
    await Task.Delay(5000);  // holding execution for 5 seconds
    Console.WriteLine("Process 1 Completed");
  }
  static void Process2() {
    Console.WriteLine("Process 2 Started");
    // Write code here
    Console.WriteLine("Process 2 Completed");
  }
}

La palabra clave async se usa para identificar el método Main() en el ejemplo anterior, y Task es el tipo de devolución.

El método se identifica como asíncrono por la palabra clave async. Recuerde que todos los métodos de la cadena de métodos deben ser asincrónicos para realizar la programación asincrónica.

Por lo tanto, para hacer que los métodos secundarios sean asíncronos, la función Main() debe ser asincrónica.

La palabra clave async marca el método Process1() como asíncrono y en espera de Task.Delay(5000); evita que el subproceso ejecute algo útil durante 5 segundos.

El método async Main() del hilo principal de la aplicación ahora inicia la ejecución del programa. El subproceso principal de la aplicación continúa ejecutando la instrucción posterior, que utiliza el método Process2() sin esperar a que finalice async Process1().

La salida de este segmento de código será:

Salida de programación asíncrona

Uso de las palabras clave async, await y Task

Si el método async devuelve un valor al código de llamada, use async junto con await y Task. En el ejemplo anterior, usamos la palabra clave async para mostrar cómo usar un método de anulación asíncrono básico.

Antes de que el método async devuelva un valor, la palabra clave await lo espera. Como resultado, hasta que recibe un valor de retorno, el subproceso principal de la aplicación se cuelga ahí.

La clase Task representa una operación asincrónica, y un proceso que puede devolver un resultado se representa mediante el tipo genérico Task<TResult>. En el caso anterior, se utilizó await Task. await retiene un hilo durante 5 segundos después de que Delay(5000) inicia una operación async que duerme durante 5 segundos.

El método async devuelve un valor en el ejemplo a continuación.

Implementar el método async y esperar tareas múltiples en C#

Considere un ejemplo en el que tenemos dos procesos, cada uno devuelve un valor, y esos valores se pasan a alguna otra función que calcula la suma de estos dos (La suma se calcula con fines ilustrativos, puede realizar cualquier operación con los resultados).

Para ello, debemos esperar a que los procesos se completen y devuelvan el valor y luego pasar esos valores devueltos a la otra función. Por lo tanto, se utiliza el método WhenAll.

El método WhenAll espera a que se completen todos los procesos y luego guarda los resultados devueltos. Esto se demuestra en el siguiente ejemplo:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class Program {
  static async Task Main(string[] args) {
    Task<int> res1 = Process1();
    Task<int> res2 = Process2();

    Console.WriteLine("After two processes.");
    var ls = await Task.WhenAll(res1, res2);
    DoSomeTask(ls);

    Console.ReadKey();
  }

  static async Task<int> Process1() {
    Console.WriteLine("Process 1 Started");
    await Task.Delay(5000);  // hold execution for 5 seconds
    Console.WriteLine("Process 1 Completed");
    return 15;
  }

  static async Task<int> Process2() {
    Console.WriteLine("Process 2 Started");
    await Task.Delay(3000);  // hold execution for 3 seconds
    Console.WriteLine("Process 2 Completed");
    return 25;
  }

  static void DoSomeTask(int[] l) {
    int sum = 0;
    foreach (int i in l) sum += i;
    Console.WriteLine("sum of the list is: {0} ", sum);
  }
}

En el segmento de código anterior, hemos creado dos funciones, Process1 y Process2. Ambas funciones tienen un retraso de 5 segundos y 3 segundos.

Cuando se completan los dos procesos, pasamos los valores devueltos a DoSomeTask,, donde calculamos la suma de estos números y los mostramos en la pantalla. La salida de este segmento de código será:

Programación asíncrona con valores de retorno