Golang es un lenguaje de programación concurrente. Tiene características poderosas como
Goroutines
y Channels
que puede manejar tareas asincrónicas muy bien. Además, las goroutines no son subprocesos del sistema operativo, y es por eso que puede activar tantas goroutines como desee sin mucha sobrecarga, su tamaño de pila comienza en solo 2 KB . Entonces por qué async/await
? Async/Await es una buena función de lenguaje que proporciona una interfaz más sencilla para la programación asíncrona.Enlace del proyecto: https://github.com/Joker666/AsyncGoDemo
Comenzó con F# y luego C#, ahora en Python y Javascript, async/await es una característica extremadamente popular de un lenguaje. Simplifica la estructura de ejecución del método asíncrono y se lee como código síncrono. Mucho más fácil de seguir para los desarrolladores. Veamos un ejemplo simple en C# de cómo funciona async/await
static async Task Main(string[] args) { Console.WriteLine( "Let's start ..." ); var done = DoneAsync(); Console.WriteLine( "Done is running ..." ); Console.WriteLine( await done); } static async Task<int> DoneAsync() { Console.WriteLine( "Warming up ..." ); await Task.Delay( 3000 ); Console.WriteLine( "Done ..." ); return 1 ; }
tenemos el
Main
función que se ejecutaría cuando se ejecute el programa. Tenemos DoneAsync
que es una función asíncrona. Detenemos la ejecución del código con Delay
función durante 3 segundos. Delay es una función asíncrona en sí misma, por lo que la llamamos con await.await solo bloquea la ejecución del código dentro de la función asíncrona
En la función principal, no llamamos
DoneAsync
con espera. Pero la ejecución comienza para DoneAsync
. Solo cuando lo esperamos, recuperamos el resultado. El flujo de ejecución se ve así Let 's start ... Warming up ... Done is running ... Done ... 1
Esto parece increíblemente simple para la ejecución asíncrona. Veamos cómo podemos hacerlo con Golang usando Goroutines y Channels
func DoneAsync () chan int { r := make ( chan int ) fmt.Println( "Warming up ..." ) go func () { time.Sleep( 3 * time.Second) r <- 1 fmt.Println( "Done ..." ) }() return r } func main () { fmt.Println( "Let's start ..." ) val := DoneAsync() fmt.Println( "Done is running ..." ) fmt.Println(<- val) }
Aquí,
DoneAsync
se ejecuta de forma asíncrona y devuelve un canal. Escribe un valor en el canal una vez que termina de ejecutar la tarea asíncrona. En main
función, invocamos DoneAsync
y seguimos haciendo nuestras operaciones y luego leemos el valor del canal devuelto. Es una llamada de bloqueo que espera hasta que el valor se escribe en el canal y después de que obtiene el valor lo escribe en la consola. Let 's start ... Warming up ... Done is running ... Done ... 1
Vemos, logramos el mismo resultado que el programa C# pero no se ve tan elegante como async/await. Si bien esto es realmente bueno, podemos hacer muchas cosas más granulares con este enfoque con mucha facilidad, también podemos implementar palabras clave asíncronas/en espera en Golang con una estructura y una interfaz simples. Probemos eso.
El código completo está disponible en el enlace del proyecto. Para implementar async/await en Golang, comenzaremos con un directorio de paquetes llamado
async
. La estructura del proyecto parece . ├── async │ └── async .go ├── main .go └── README.md
En el archivo asíncrono, escribimos la interfaz futura más simple que puede manejar tareas asíncronas.
package async import "context" // Future interface has the method signature for await type Future interface { Await() interface {} } type future struct { await func (ctx context.Context) interface {} } func (f future) Await () interface {} { return f.await(context.Background()) } // Exec executes the async function func Exec (f func () interface {}) Future { var result interface {} c := make ( chan struct {}) go func () { defer close (c) result = f() }() return future{ await: func (ctx context.Context) interface {} { select { case <-ctx.Done(): return ctx.Err() case <-c: return result } }, } }
No está sucediendo mucho aquí, agregamos un
Future
interfaz que tiene la Await
firma del método. A continuación, agregamos un future
estructura que contiene un valor, una firma de función de la await
función. Ahora futute
implementos de estructura Future
el método Await de la interfaz invocando su propio await
función.A continuación en el
Exec
función, ejecutamos la función pasada de forma asíncrona en goroutine. Y le devolvemos el await
función. Espera a que el canal se cierre o el contexto para leer. Según lo que suceda primero, devuelve el error o el resultado, que es una interfaz.Ahora armado con este nuevo paquete asíncrono, veamos cómo podemos cambiar nuestro código go actual
func DoneAsync () int { fmt.Println( "Warming up ..." ) time.Sleep( 3 * time.Second) fmt.Println( "Done ..." ) return 1 } func main () { fmt.Println( "Let's start ..." ) future := async.Exec( func () interface {} { return DoneAsync() }) fmt.Println( "Done is running ..." ) val := future.Await() fmt.Println(val) }
A primera vista, parece mucho más limpio, no estamos trabajando explícitamente con goroutine o canales aquí. Nuestro
DoneAsync
La función ha sido cambiada a una naturaleza completamente síncrona. En la función principal, usamos el async
paquete Exec
método para manejar DoneAsync
. que inicia la ejecución de DoneAsync
. El flujo de control se devuelve a main
función que puede ejecutar otras piezas de código. Finalmente, hacemos una llamada de bloqueo a Await
y volver a leer los datos.Ahora el código se ve mucho más simple y fácil de leer. Podemos modificar nuestro paquete asíncrono para incorporar muchos otros tipos de tareas asíncronas en Golang, pero por ahora nos limitaremos a una implementación simple en este tutorial.
Hemos revisado qué async/await it e implementamos una versión simple de eso en Golang. Le animo a que busque mucho más en async/await y vea cómo puede facilitar la legibilidad del código base mucho mejor.