CXWorkerHost

From cxwiki

The CXWorkerHost system provides a mechanism for short-lived tasks to be run in an asynchronous manner without the overhead of starting a new thread for each task, and without the problem of starting excessive numbers of new threads if a large number of such tasks need to be rapidly queued. Each new task is placed into a queue and may be tagged with a non-unique "cookie" that optionally allows the owning system to identify the task(s) later.

 

There are a variety of functions which each perform the same role of enqueuing a task for later execution. Execution may begin at any point after the function is called- which includes before the function returns. The cookie may be unique to the task, may be unique to the owning system, or may be some shared value. It is safe to pass a null cookie if there is no need to reference the task at a later time.

// Enqueue an async worker task.
void EnqueueTask(const void* workerCookie, const WorkerFunction& workerFunction);

// Enqueue a worker task which must be run on the main thread.
void EnqueueTaskOnMainThread(const void* workerCookie, const WorkerFunction& workerFunction);

// Enqueue a worker task which must be run on a specific worker thread.
void EnqueueTaskOnThread(CXThreadID threadID, const void* workerCookie, const WorkerFunction& workerFunction);

 

Similarly, it is possible to enqueue a task and wait for its completion. This obviously doesn't help with performing asynchronous work, but it is useful in ensuring that specific functionality is performed on specific threads, typically for cases where non-thread-safe objects need to be accessed.

// Enqueue a task on the main thread, and block until it has completed execution.
template <class RETURNTYPE> RETURNTYPE EnqueueTaskOnMainThreadAndWait(const void* workerCookie, const cx_function<RETURNTYPE (void)>& workerFunction);

// Enqueue a task on the main thread, and block until it has completed execution.
void EnqueueTaskOnMainThreadAndWait(const void* workerCookie, const cx_function<void (void)>& workerFunction);

 

When enqueuing a task against a specific thread, it is important to understand a number of restrictions:

  • The thread must be acting as a worker thread in order to perform the task.
    • All worker threads created internally by CXWorkerHost start in this state, however they cannot run your task while they are already running another task. Be careful of deadlocks.
    • When cxsource is running a standard UI Event-Based Application, the main thread will process tasks as required as long as it is not already doing something else. Be careful of deadlocks.
    • Other threads (for example, application threads created using CXThread) do not normally run tasks, and must explicitly call CXWorkerHost APIs to allow them to run any queued tasks.
      • If the thread is not written to make these API calls at the appropriate time, your task may not run in a timely fashion, or may not run at all.
  • If the thread terminates before it gets a chance to run your task, your task may never be run, or may be reassigned to another thread.

It is your responsibility to ensure that you only enqueue tasks against threads which are prepared to receive them. You are a lot less likely to get yourself into a messy deadlock scenario if you enqueue a task for asynchronous execution rather than waiting on task completion.

 

Once a task has been queued, it is your responsibility to ensure that any resources it needs for execution are not released until it has completed execution. This is often achieved by passing a pointer to the owning object as the task cookie, and calling CancelOutstandingTasks() or WaitOnTasks() at the start of the owning object's destructor. It is sometimes the case that a task is passed a data payload which needs to be cleaned up at the end of task execution; in this event either CancelOutstandingTasks() should not be used, or an alternative cleanup mechanism must be provided.

// Cancel any queued tasks which have the specified cookie. If 'bShouldAwaitCompletion' is set,
// this function will block until any matching executing tasks have completed. Otherwise, this
// function will simply return with the matching tasks still executing. This function does not
// prevent additional tasks from being queued with the specified cookie.
void CancelOutstandingTasks(const void* __nullable workerCookie, bool bShouldAwaitCompletion);

// Block until all tasks matching the specified cookie have completed execution. This does not
// prevent additional tasks from being queued after WaitOnTasks() completes. If an idle function
// is supplied, it is called regularly during the wait. If it returns false, the wait is aborted
// and WaitOnTasks() returns without enforcing completion.
void WaitOnTasks(const void* __nullable workerCookie, const cx_function<bool(void)>& idleFunction = cx_function<bool(void)>());

If there are outstanding tasks in the queue, WaitOnTasks() may opt to execute them itself, helping to reduce the chance of CXWorkerHost thread depletion. The caller must be aware of this possibility for deadlock-avoidance reasons.

While WaitOnTasks() is waiting, it may execute any tasks which are destined for the waiter thread, regardless of the tasks' cookies, helping to reduce the chance of a deadlock where the waitee task is in turn waiting on the waiter thread. The caller must be aware of this possibility for deadlock-avoidance reasons.

If WaitOnTasks() is called on a CXFiber, it may put the fiber to sleep.