Código asíncrono y reactivo

Funciones async, await y tareas, y campos reactivos, valores calculados y efectos en Antril.

Última actualización: 5 min de lectura Editar en GitHub

En esta página

async y await

Una función marcada con async devuelve un Task (sin resultado) o un Task<T>. Dentro de ella, await espera a otra tarea sin bloquear a las demás; la función continúa donde se detuvo cuando esa tarea termina.

AntrilRun
import System;
import System.Threading.Tasks;

into namespace Jobs
{
	async Fetch(i32 id) : Task<i32>
	{
		await Task.Delay(10);
		return id * 10;
	}

	async Total() : Task<i32>
	{
		i32 a = await Fetch(1);
		i32 b = await Fetch(2);
		return a + b;
	}

	async Tick(str name, i32 times) : Task
	{
		loop (i32 i = 0..times)
		{
			Console.WriteLine(name + " " + i);
			await Task.Yield();
		}
	}

	Main()
	{
		Console.WriteLine(Total().Result);
		Task[] both = [Tick("a", 2), Tick("b", 2)];
		Task.WhenAll(both).Wait();
	}
}
Terminal
larva run
30
a 0
b 0
a 1
b 1
  • async se usa en funciones libres y en métodos de clases. await solo se permite dentro de una función async.
  • Task<T> necesita un T que se pueda copiar (T : copy).
  • Las funciones asíncronas se turnan en un solo hilo: System.Threading.Tasks.Scheduler ejecuta lo que esté listo, y los temporizadores.
  • Wait() bloquea hasta que una tarea termina, y Result espera y entrega su valor. Úsalos donde no estés dentro de una función async, como en Main.
Miembro de Task Qué hace
Task.Delay(ms) Una tarea que termina después de ms milisegundos.
Task.Yield() Deja que otras tareas se ejecuten antes de continuar.
Task.WhenAll(tasks) Una tarea que termina cuando todas las de tasks han terminado.
Task<T>.FromResult(value) Un Task<T> que ya está completo.
Task.Run(f), Task.Run<T>(f) Una tarea que ejecuta la función f.
Wait(), Result, IsCompleted, IsFaulted Esperar una tarea y consultar su estado.

Excepciones

Una excepción lanzada en una función async se guarda en su tarea. await, Wait() y Result la vuelven a lanzar, tal como era:

AntrilRun
import System;
import System.Threading.Tasks;

async Failing() : Task<i32>
{
	await Task.Yield();
	throw new InvalidOperationError("no data");
}

async Rescue() : Task<i32>
{
	try
	{
		return await Failing();
	}
	catch (InvalidOperationError e)
	{
		Console.WriteLine("failed: " + e.Message);
		return -1;
	}
}

Main()
{
	Console.WriteLine(Rescue().Result);
}
Terminal
larva run
failed: no data
-1

Para trabajar en varios hilos a la vez, consulta System.Threading en la descripción general de la biblioteca.

Código reactivo

Las clases pueden tener estado reactivo: campos que notan cuándo se leen y cuándo cambian, de modo que el código que depende de ellos se vuelve a ejecutar solo.

Declaración Qué es
reactive i32 count = 0; Un campo que es una señal. Leerlo dentro de un efecto hace que el efecto dependa de él; asignarlo vuelve a ejecutar esos efectos.
computed i32 Double => count * 2; Un valor calculado a partir de otros. Quien lo lee depende de lo que él lee.
effect { … } Se ejecuta ahora y otra vez cada vez que cambia algo que leyó. Entrega un System.Reactive.Effect; Dispose() lo detiene.
reacts Log() { … } Un método que se ejecuta como efecto una vez que su objeto está construido.
AntrilRun
import System;
import System.Reactive;

into namespace Ui
{
	public class Cart
	{
		public:
			reactive i32 items = 0;
			reactive f64 price = 2.5;
			computed f64 Total => items * price;

			reacts Log()
			{
				Console.WriteLine("items: " + items);
			}
	}

	Main()
	{
		Cart cart = new Cart();
		Effect shown = effect { Console.WriteLine("total: " + cart.Total); };
		cart.items = 2;
		Reactive.Batch(() => { cart.items = 3; cart.price = 4.0; });
		shown.Dispose();
		cart.items = 10;
	}
}
Terminal
larva run
items: 0
total: 0
items: 2
total: 5
items: 3
total: 12
items: 10
  • Reactive.Batch(f) ejecuta f y después cada efecto afectado una sola vez, en lugar de una vez por cambio.
  • Un efecto no se vuelve a ejecutar mientras se está ejecutando, así que un efecto que escribe lo que lee no entra en un ciclo.
  • Un efecto ve las variables locales que usa tal como están cuando se ejecuta, igual que una lambda.
  • Los miembros reactivos pertenecen a las clases, no a los tipos value.

Siguiente: la referencia, que describe en detalle cada construcción del lenguaje.

¿Te resultó útil esta página?

No se guarda ninguna dirección; tu nombre de usuario sí, si iniciaste sesión.

Esc