Skip to content

Coroutine task and context

Simon Krajewski edited this page Jan 1, 2026 · 1 revision

We can think of a task as a unit of execution, similar to a thread but much more lightweight and not inherently concurrent. Tasks are usually somewhat hidden behind the hxcoro API because direct interaction with them can easily go wrong, but we can make them visible via the omnipresent context:

import hxcoro.task.CoroTask;
import hxcoro.CoroRun;

function main() {
	CoroRun.runScoped(node -> {
		final task = node.context.get(CoroTask.key);
		trace(task.id, task.isActive()); // 1, true
	});
}

Let us accept the presence of runScoped and node as "something to manage tasks" for the time being until we learn more about it when discussing structured concurrency. The lessons here are:

  • hxcoro executes coroutines in a task. Whenever we enter "coroutine world" through one of the functions in CoroRun, a task is created to manage execution.
  • Each task has an immutable context to manage data associated with it. We can see in the example above that the task itself is an entry in its own context and can be accessed by using the appropriate key, CoroTask.key. Another important default element of all task contexts is a scheduler to manage asynchronous execution, which can be accessed with the haxe.coro.schedulers.Scheduler.key key.
  • Tasks internally deal with various aspects of coroutine execution and we generally discourage direct interaction with them unless you really know what you're doing, which you probably don't if you're reading this! Most of their actual public functionality is defined by interfaces, such as ICoroNode via the node argument in the example above.

Task context

As mentioned above, the context of a task is immutable, which means that it doesn't change once the task has been created. We can modify the context before creating the task in order to associate values with it, which we usually call components:

import hxcoro.components.CoroName;
import hxcoro.CoroRun;

function main() {
	CoroRun.with(new CoroName("My coro")).create(node -> {
		trace(node.context.get(CoroName.key).name); // My coro
	}).start();
}

CoroName is a built-in component that allows associating a name with a task, which can sometimes be useful for debugging. Hxcoro supports the usage of with in order to add components to the context, with the latest one taking priority:

import hxcoro.components.CoroName;
import hxcoro.CoroRun;

function main() {
	CoroRun.with(new CoroName("My coro"), new CoroName("My better coro")).create(node -> {
		trace(node.context.get(CoroName.key).name); // My better coro
	}).start();
}

Using a different scheduler

Since we already established that schedulers are a context element as well, it stands to reason that we can modify the scheduler of a task too. Indeed, the example just above does not have a scheduler in its context, so any asynchronous operation would fail. We can manually add a scheduler as a component:

import hxcoro.schedulers.VirtualTimeScheduler;
import hxcoro.Coro.*;
import hxcoro.CoroRun;

function main() {
	final scheduler = new VirtualTimeScheduler();
	CoroRun.with(scheduler).create(node -> {
		trace("0");
		delay(2);
		trace("2");
		delay(1);
		trace("3");
	}).start();

	for (i in 1...4) {
		trace('Advanding to $i');
		scheduler.advanceTo(i);
	}
}

Running this creates this output:

source/Main.hx:8: 0
source/Main.hx:16: Advanding to 1
source/Main.hx:16: Advanding to 2
source/Main.hx:10: 2
source/Main.hx:16: Advanding to 3
source/Main.hx:12: 3

The VirtualTimeScheduler used here does not react to real time, but instead has to be advanced manually. Without the for loop at the bottom, only the 0 would trace and the program would terminate afterwards.

Clone this wiki locally