Async and reactive code

async functions, await and tasks, and reactive fields, computed values and effects in Antril.

Last updated 4 min read Edit on GitHub

On this page

async and await

A function marked async returns a Task (no result) or a Task<T>. Inside it, await waits for another task without blocking the others; the function continues where it stopped when that task completes.

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 goes on free functions and on methods of classes. await is only allowed inside an async function.
  • Task<T> needs a T that can be copied (T : copy).
  • Async functions take turns on one thread: System.Threading.Tasks.Scheduler runs whatever is ready, and the timers.
  • Wait() blocks until a task completes, and Result waits and gives its value. Use them where you are not in an async function, such as Main.
Task member What it does
Task.Delay(ms) A task that completes after ms milliseconds.
Task.Yield() Lets other tasks run before continuing.
Task.WhenAll(tasks) A task that completes when all of tasks have.
Task<T>.FromResult(value) A Task<T> that is already complete.
Task.Run(f), Task.Run<T>(f) A task that runs the function f.
Wait(), Result, IsCompleted, IsFaulted Waiting for a task and asking about it.

Exceptions

An exception thrown in an async function is kept in its task. await, Wait() and Result throw it again, as it was:

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

For work on several threads at once, see System.Threading in the library overview.

Reactive code

Classes can hold reactive state: fields that notice when they are read and changed, so code that depends on them runs again by itself.

Declaration What it is
reactive i32 count = 0; A field that is a signal. Reading it inside an effect makes the effect depend on it; assigning it runs those effects again.
computed i32 Double => count * 2; A value computed from others. Whoever reads it depends on what it reads.
effect { … } Runs now and again each time something it read changes. It gives a System.Reactive.Effect; Dispose() stops it.
reacts Log() { … } A method that runs as an effect once its object is constructed.
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) runs f and then each affected effect once, instead of once per change.
  • An effect does not run again while it is running, so an effect that writes what it reads does not loop.
  • An effect sees the local variables it uses as they are when it runs, like a lambda.
  • Reactive members belong to classes, not to value types.

Next: the reference, which describes every construct of the language in detail.

Was this page helpful?

No address is stored; your user name is, if you are signed in.

Esc