Skip to content

Coroutine task and context

Simon Krajewski edited this page Oct 28, 2026 · 3 revisions

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.CoroBaseTask;
import hxcoro.CoroRun;

function main() {
	CoroRun.run(node -> {
		final task = node.context.get(CoroBaseTask.key);
		trace(task.id, task.isActive());
	});
}

This traces 1, true. Let us accept the presence of 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, CoroBaseTask.key. Another important element of all task contexts is a dispatcher to manage asynchronous execution, which can be accessed with context.get(haxe.coro.dispatchers.Dispatcher). The dispatcher in turn holds a scheduler, which manages time-related behavior.
  • 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 elements:

import hxcoro.elements.CoroName;
import hxcoro.CoroRun;

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

CoroName is a built-in element that allows associating a name with a task, which can sometimes be useful for debugging. CoroRun.with creates a context containing the given elements, and CoroRun.runWith runs a task with it. Hxcoro supports the usage of with in order to add elements to the context, with the latest one taking priority:

import hxcoro.elements.CoroName;
import hxcoro.CoroRun;

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

Using a different scheduler

Since we already established that the dispatcher is a context element as well, it stands to reason that we can modify the dispatcher of a task too. CoroRun.runWith always installs its own dispatcher, but we can create and start a task in a context of our own choosing by using launchTask from hxcoro.run.ContextRun. A context created by CoroRun.with does not have a dispatcher, so we have to add one ourselves, together with the scheduler it should use:

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

using hxcoro.run.ContextRun;

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

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

Running this creates this output:

source/Main.hx:12: 0
source/Main.hx:20: Advancing to 1
source/Main.hx:20: Advancing to 2
source/Main.hx:14: 2
source/Main.hx:20: Advancing to 3
source/Main.hx:16: 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.

The TrampolineDispatcher executes continuations on the current thread. hxcoro.run.Setup contains functions that create common combinations of dispatchers and schedulers, such as Setup.createVirtualTrampoline() for the combination used here.

Clone this wiki locally