diegoRodicio

Está documentación está a túa disposición sin ningún custo económico. Sen embargo, para a súa elaboración dedico moito tempo e recursos, polo que agradecería unha colaboración co que consideres oportuno. Gracias.

A concorrencia pódese definir como a capacidade do software para realizar múltiples tarefas en paralelo.

Moitos proxectos de desenvolvemento de aplicacións terán que facer uso do procesamento simultáneo nalgún momento e a concorrencia é esencial para proporcionar unha boa experiencia de usuario. A

concorrencia, por exemplo, é o que permite que a interface de usuario dunha aplicación siga sendo sensible mentres realiza tarefas en segundo plano, como a descarga de imaxes ou o procesamento de datos.

Concurrencia vs código síncrono #

Antes de explorar a concorrencia, primeiro analizaremos un exemplo de execución de código non concorrente (tamén coñecido como código síncrono):

func facerAlgo() {
    print("Inicio: \(Date())")
    moitoTempo()
    
    //Tarefa síncrona (as tarefas se executan unha detrás de outra: Ata que non se executa a liña anterior, non visulizamos esta:
    print("Fin: \(Date())")
}
func moitoTempo() {
    //Espera 5 segundos
    Thread.sleep(forTimeInterval: 5)
    print("Tarefa síncrona completada: \(Date())")
}

facerAlgo()

Se executamos ese código, vemos un resultado parecido ao seguinte (cambiando as datas e horas que saen na imaxe polas datas e horas nas que executamos o programa):

Se vos fixade, a hora de finalización é 5 segundos despois da hora de inicio:

  • A chamada a moitoTempo() durou 5 segundos, como se esperaba
  • Calquera código despois de que se fixese a chamada a moitoTempo() dentro da función facerAlgo() non se executa ata que remata a execución de moitoTempo().

Para solventar este problema empregaremos a estrutura de concorrencia Swift async/await.

async/await #

A palabra chave async utilízase ao declarar unha función para indicar que debe executarse de forma asíncrona.

  • O código da función debe executarse nun fío diferente ao do que se chamou.
  • Notifica ao sistema que a función pode suspenderse durante a execución para permitir que o sistema execute outras tarefas.
  • Se unha función asíncrona chama a outras funcións asíncronas, a función principal non pode saír ata que todas as tarefas secundarias tamén se completaron.

As funcións asíncronas xeralmente só se poden chamar desde outras funcións asíncronas. Podemos utilizar Task para proporcionar unha ponte entre o código síncrono e asíncrono.

O máis importante é que unha vez que se declarou unha función como asíncrona, só pódese chamar usando a palabra chave await. Vemos entón o exemplo:

func facerAlgoAsincrono() async {
    print("Inicio: \(Date())")
    await moitoTempoAsincrono()
    
    print("Fin: \(Date())")
}

func moitoTempoAsincrono() async {
    //Espera 5 segundos
    Thread.sleep(forTimeInterval: 5)
    print("Tarefa asíncrona completada: \(Date())")
}

Task {
    await facerAlgoAsincrono()
}

Problema… #

Como probablemente notases, a función facerAlgoAsincrono() esperou ata que a función moitoTempoAsincrono() rematase. De feito, a tarefa realizouse nun fío diferente, pero a palabra chave await díxolle ao sistema que esperase ata que se completase:

Como se mencionou anteriormente, unha función asíncrona principal non pode completarse ata que todas as súas subfunciones tamén se completen.

async let #

No exemplo anterior, identificamos que o comportamento predeterminado da palabra chave await é esperar a que volva a función chamada antes de renovar a execución.

O máis normal é que desexemos continuar executando código mentres a función asíncrona execútase en segundo plano. Isto pódese lograr aprazando a espera ata máis adiante no código utilizando unha ligazón asíncrona (async let).

Así, modificamos o exemplo anterior do seguinte xeito:

  • Modificamos a función moitoTempoAsincrono() para que devolva a hora (liñas 13 e 16)
  • Gardamos o resultado da función anterior dunha variable empregando async let (liña 4)
  • Accedemos á variable anterior mediante await (liña 8)
func facerAlgoAsincrono() async {
    print("Inicio: \(Date())")
    
    async let resultado = moitoTempoAsincrono()
    
    print("Hora de remate da tarefa asíncrona: \(Date())")
    
    print("resultado = \(await resultado)")
    
    print("Fin: \(Date())")
}

func moitoTempoAsincrono() async -> Date{
    //Espera 5 segundos
    Thread.sleep(forTimeInterval: 5)
    return Date()
}

Task {
    await facerAlgoAsincrono()
}

Ao imprimir o valor do resultado (liña 8), estamos a usar await para facerlle saber ao sistema que a execución non pode continuar ata que a función asíncrona moitoTempoAsincrono() regrese co valor do resultado. A execución deterase ata que o resultado estea dispoñible.

Con todo, calquera código entre o async let e o await executarase simultaneamente coa función moitoTempoAsincrono():

Manexo de erros #

No seguinte exemplo, veremos o manexo de errores con concorrencia:

enum DuracionError:Error{
    case moitoTempo
    case poucoTempo
}

func facerAlgoAsincronoConMaxenoErros() async {
    print("Inicio: \(Date())")
    
    do {
        try await moitoTempoAsincronoConMaxexoErros(delay: 25)
    }catch DuracionError.poucoTempo{
        print("ERRO: Pouco tempo")
    }catch DuracionError.moitoTempo{
        print("ERRO: Moito tempo")
    }catch{
        print("ERRO: Erro descoñecido")
    }
    
    print("Fin: \(Date())")
}

func moitoTempoAsincronoConMaxexoErros(delay: TimeInterval) async throws{
    
    if delay<5 {
        throw DuracionError.poucoTempo
    }else if delay>20 {
        throw DuracionError.moitoTempo
    }

    Thread.sleep(forTimeInterval: delay)
    print("Tarefa asíncrona completada: \(Date())")
}

Task {
    await facerAlgoAsincronoConMaxenoErros()
}


Share Your Valuable Opinions