Preliminary. ThinkMeta ConcurrentTasks and this documentation are preliminary; everything is subject to change.

Tasks

A task is an object whose Run method the scheduler calls on one of its threads. The object belongs to you: the scheduler never allocates, copies or deletes it.

The interface

At the bottom is ITask from Core.h:

Member Purpose
Run The task's body. noexcept; it may wait, yield and sleep.
OnTaskExited Called by the core once the task has exited — the earliest moment the object may be destroyed or added again.
SetNotifyCallback Lets code that did not create the task observe its end (a task group uses it).
GetTimerData Storage for starting the task by time; nullptr means it cannot be started later.

The library's task classes

You rarely implement ITask yourself:

  • Task — the base class to derive from. It provides the timer storage, forwards the exit notification and adds Schedule (add to the calling task's scheduler, optionally after a delay).
  • WaitableTask / WaitableTask_s — a Task that others can Wait for.
  • LambdaTask — a Task that runs a callable; Task::Create builds one. It is returned by value and not scheduled yet: keep it in a variable that outlives the task.

Running a task

AddTask (or Schedule) queues the task; a scheduler thread calls Run on a fiber. Run runs once per AddTask. While it runs, the task may:

  • wait on a primitive or for I/O — it is suspended and the thread runs other tasks;
  • yield — stay runnable but let others go first;
  • sleep — with timers enabled.

When Run returns, the task has exited: the core calls OnTaskExited, and from then on it no longer touches the object.

Important

A task object must stay alive and in place from AddTask until its OnTaskExited has been called, and may be added again only after that. A task that waits on a primitive must not outlive it either — see Lifetimes and sizing.

Exceptions

Run is noexcept. An exception that escapes it ends the process. Catch what you expect inside the task and report it through the task's own state.

A task may change threads

Each time a task is resumed, any thread of its scheduler may pick it up. Code in a task therefore must not keep thread-affine state across a wait: no OS locks held over a Wait (std::mutex, CRITICAL_SECTION, SRWLOCK), no assumptions about thread_local values or the thread id. Use the library's CriticalSection, which is owned by the task, not by the thread.

See also