AIStatefulTask ‐ Asynchronous, Stateful Task Scheduler library. Threads-like task objects evolving through user-defined states. |
A typical Task will look like,
Then in multiplex_impl each state need to be implemented. Here are a few examples.
It is simply the last call to set_state that is used to determine what state to run the next invocation of multiplex_impl. Also, as might be intuitively correct, it is not really necessary to return from multiplex_impl to change state; you are allowed to simply fall-through to the next state (even without calling set_state).
If in the above code you'd have used a break instead of falling through, then the program would have almost acted in the same way: upon return from multiplex_impl the engine sees that the task is still running and will therefore immediately reenter multiplex_impl.
In other words, doing a break is not the same as a yield.
Even if a task runs in an engine with a max_duration, and it would go over that time limit then doing a break still doesn't do anything but immediately reentering multiplex_impl to continue with the next state. The test that looks if the engine did run for too long only is done once we actually return to the mainloop() of the engine which only happens when either wait or yield is called.
Hence, if you want this time check to take place, or if you simply want other tasks in the same engine to get a chance to run too while this task is working, call yield*(). For example,
Finally there are a couple of typical ways to go idle while waiting for some event to happen. Under the hood all of those use the same mechanism: you call wait(condition) and the task goes idle until something else calls task.signal(condition).
Here condition is simply a uint32_t bit mask. Normally you will just use 1
. In order not to wake up when some old signal happens for a condition that you are no longer waiting for, each task has up to 32 different possible values. If you were waiting for mask 1
and it didn't come or might still be coming (again) but now you want to wait for something else, then simply wait for condition 2
, 4
or 8
etc so that you will automatically ignore an (old and lagging behind) signal on 1
. It is possible to wait and/or signal multiple conditions at the same time however: a call to wait(condition1) is woken up by a call to signal(condition2) when condition1 & condition2 is non-zero.
For example,
Often you want to wait for a real condition however, for example x > y, and it is not really possible to call signal when that happens and then still be sure that this condition is still true once the task starts running again.
In general, you will only have events that when they happen make it possible, preferably likely that the condition that you are waiting for is true.
Code that needs this will typically look like this:
Simply running another task and waiting until it is finished:
Running some computational extensive function in another thread (see AIPackagedTask):
Note that upon a successful queue by dispatch, wait_until was already called on the current task; no additional call to wait is necessary here.
AIPackagedTask also has a constructor that allows using a member function of some object to be used as callback.