Skip to main content

Threading Framework

The threading framework enables the use of asynchronous calling. It is useful when a background thread is used to fetch data, perform calculation, performing custom value rendering, or even have custom visualizations render on a the background thread.

Overview

The threading framework is based on the idea of a client submitting work items to the framework and receiving callbacks when the work is done. On the other end, workers are created on background threads and are fed the work items. They then perform the work specified by the work item and report the result back to the framework. The framework forwards the result to the client. Note hat the client callback will happen on the background thread, not the application thread.

See also:
  • Creating Threads
    Threading is used when some work needs to be performed asynchronously. Properly used it may enhance the end user experience considerably, but it also requires a fair amount of work, so it is only recommended for scenarios where it will make a difference.
  • How to Create a Thread
    This example describes a simple thread implementation.

Key Concepts

The key concepts are best introduced by a typical call sequence:

Sequence diagram for the process of creating a thread

In this sequence diagram, the client calls CreateDispatcher on the work manager with a worker model and a worker factory. The manager decides that a new thread is needed and tells the thread to create a worker. Finally, a dispatcher is created that is bound to the worker and worker model.

The dispatcher is then returned to the client. Later, the client creates a work item and pushes a result handler on it. The client dispatches the work item through the dispatcher. The dispatcher takes a snapshot of the worker model and signals that a work item is available. The thread passes the work item and worker model to the worker.

When the work is done, the ReturnItem method calls the result handler. As noted above, the client gets this callback on the worker thread, not the application thread.

WorkManager

A global service used by the API client to create work dispatchers. It also coordinates the creation of background threads and workers. When creating a work dispatcher, the client can optionally specify a worker model. The worker model is a document node that is passed to the worker.

WorkDispatcher

The work dispatcher manages a queue of work items, while tracking changes to the worker model. When a change is detected, a model generation counter is ticked and the WorkerModelChanged event is raised.

The dispatcher also has an AutoCancel property that if true (default) calls CancelAll before the event is raised. When the event is handled by the client, it would typically clear any cached results and invalidate the UI.

Worker

The worker is created on a background thread. All calls to it are made from that thread.

The worker has a DoWorkCore method where the work is done. When passed a read-only snapshot of the worker model document node and a work item, the worker can perform its work by reading from the model and work item. The result is put in the work item.

If the worker model is not needed, it can be set to null by the client.

Batching

The worker has a DoBatchWorkCore method and a MaxBatchSize property to support larger chunks of work. By setting MaxBatchSize to a value greater than one and overriding DoBatchWorkCore, several work items can be processed in one go.

DoBatchWorkCore is passed a list of work items. The default implementation calls ReadModel on the first work item and then simply calls DoWorkCore for each item. It also calls ReturnItem on the work items which signals that the the item should be returned to the client.

Chaning Workers

In some advanced scenarios, the worker uses additional workers to do part of the work. This implies that the result will be assembled in a callback from yet another thread. The crucial point here is to defer calling ReturnItem, by overriding DoBatchWorkCore until the result has been assembled.

WorkItem

The work item carries information to the worker and back to the client. The implementer subclasses the work item and adds input and result fields to it. The model generation can be used by the worker to cache model information and invalidate the cache when the model generation has changed. The ReadModel method executes an action delegate which is passed the snapshot of the model.

Result handlers

Before adding a work item to the dispatcher, the client calls PushResultHandler on the work item. When the worker calls ReturnItem the handlers are popped and run by the framework. Note that each handler must call ReturnItem when it is done, since the handler may switch asynchronously to another thread and process the work item result there. A common scenario is to switch to the UI thread and populate a user control with the result.