DEV Community

GoyesDev
GoyesDev

Posted on

SC #5: Ejecución asíncrona, serial y concurrente

func fetchData(_ id: Int) async throws -> String {
  try await Task.sleep(for: .seconds(1))
  return "\(id)"
}
let result1 = try await fetchData(1)
let result2 = try await fetchData(2)
let result3 = try await fetchData(3)
Enter fullscreen mode Exit fullscreen mode

En el código anterior, las peticiones se ejecutan de arriba a abajo, una después de otra, como si fuera código secuencial.

Ejecución paralela

A veces lo más óptimo es iniciar varias tareas asíncronas de forma concurrente, para lo que se usa async let.

Cada async let crea una subtarea que se ejecuta en paralelo, y se ejecuta inmediatamente se define. Además, async let permite que el "scope" que define el futuro o promesa continúe con la ejecución.

func fetchData(_ id: Int) async throws -> String {
  try await Task.sleep(for: .seconds(1))
  return "\(id)"
}
func fetchAllData() async throws {
  async let future1 = fetchData(1)
  async let future2 = try await fetchData(2)
  async let future3 = fetchData(3)

  let results = try await [future1, future2, future3]
  print(results) // ["1", "2", "3"] 
}
Enter fullscreen mode Exit fullscreen mode

Mientras que las peticiones se definen de arriba a abajo, Swift las despacha de forma concurrente sin garantizar el orden de ejecución - En otras palabras: el orden de ejecución es indeterminado.

Al declarar una constante como async let, es redundante llamar try await en la subtarea.

async let future1 = fetchData(1)
async let future2 = try await fetchData(1)
Enter fullscreen mode Exit fullscreen mode

Ejecución de async let

Aunque parezca contra-intuitivo, un método marcado con async let se invoca inmediatamente. la diferencia con respecto a await es que no se espera por el resultado, lo que provoca que el contexto contenedor continúe.

Manejo de errores de async let

Si uno de los llamados de async let arroja un error, las otras tareas del grupo continúan ejecutándose hasta que se marque explícitamente await sobre el grupo de tareas.

func fetchDataWithPotentialFailure(_ id: Int) async throws -> String {
  if id == 2 {
    // Simulating an error for id `2`.
    throw URLError(.badServerResponse)
  }
  return "Data \(id)"
}

func loadAllDataWithFailure() async {
  async let data1 = fetchDataWithPotentialFailure(1)
  async let data2 = fetchDataWithPotentialFailure(2) 
  async let data3 = fetchDataWithPotentialFailure(3)

  do {
    let results = try await [data1, data2, data3]
    print(results)
  } catch {
    print("Error occurred: \(error)")
  }
}
Enter fullscreen mode Exit fullscreen mode

Al hacer await y esperar los resultados, se encuentra que data2 falló, lo que implícitamente cancela data1 y data2, si estos métodos respetan correctamente la cancelación.

async let no detiene la ejecución de forma inmediata cuando hay un error, sino hasta que el grupo se espera explícitamente.

Cancelación de un async let

Si el scope de una tarea compuesta de async let se acaba o si hay un error, entonces los async let se cancelan.

Esto quiere decir que si no hay await dentro de una función async, el scope va a terminar, cancelando las subtareas async let de forma implícita.

El siguiente código es válido:

async let _ = fetchData(1)
Enter fullscreen mode Exit fullscreen mode

Diferencia entre async let y Task

Task crea un trabajo concurrente sin estructura, mientras que async let asegura que el las subtareas se ejecutan eficientemente con el pool de hilos de Swift:

  1. Optimizando la creación de hilos
  2. Asegurando el uso óptimo de CPU
  3. Reduciendo el sobreuso de memoria en comparación con las tareas creadas manualmente.

¿Cuándo usar async let?

  • Si las llamas asíncronas no dependen entre sí y se pueden correr de forma concurrente.
  • Si se conoce el número de tareas concurrentes en tiempo de compilación.
  • Como async let se cancela cuando está fuera el scope del método, se considera más segura.

¿Cuándo NO usar async let?

  • Si se necesita manejo dinámico de tareas, se puede usar TaskGroup.
  • No se necesita ejecución paralela.
  • Si se necesita cancelar las tareas manualmente, se puede usar TaskGroup.

Top comments (0)