Asynchronous UI

A modern UI should avoid showing wait cursors to the user, and where necessary perform time consuming activities such as computation in a worker thread. The main thread should also tend to avoid performing I/O - particularly if it involves communication over a network. Otherwise the UI can feel unresponsive or sluggish to the user. Even blocking the main thread for just 50 milliseconds can impact the rate at which data entry can be performed by users.

Posting tasks to the main UI thread

It is useful for application programs to have a means to allow any thread to post a lambda function to a queue which is executed some time in the future by the main UI thread. This might have the following signature:


void PostTaskToMainUI( std::function<void()> task );

For example, a Windows application could implement this function in terms of the Win32 function PostMessageW, perhaps using WM_USER for the Msg parameter:


BOOL PostMessageW(
  [in, optional] HWND   hWnd,
  [in]           UINT   Msg,
  [in]           WPARAM wParam,
  [in]           LPARAM lParam
);

Posting tasks to a worker thread

Consider that there is a function available to any thread to post tasks to a worker thread (or a thread pool):


void PostTaskToWorkerThread( std::function<void()> task );

This may cause the posted tasks to be queued.

Synchronous version

Let there be a pure function which calculates an output for a given input:


Output CalculateOutput(Input input);

Consider the following event handler which is called by the main UI thread and which retrieves the input (perhaps from a model in an MVC design), directly calls CalculateOutput then updates the UI (perhaps by updating derived variables in a model):


// Synchronous version which might make the UI feel unresponsive
void MyUI::OnMouseButtonUp()
{
    Input input = CalculateInput();
    Output output = CalculateOutput(input);
    UpdateUI(output); 
}

Asynchronous version

Let's assume CalculateOutput takes a long time, so that we cannot afford to call it from the main UI thread. Fortunately, since this is a pure function which doesn't access any global state, it can safely be called by a worker thread. Furthermore it is assumed the types Input and Output are value types supporting default construction, copy and move construction and copy and move assignment.


// Asynchronous version which makes the UI very responsive
void MyUI::OnMouseButtonUp()
{
    Input input = CalculateInput();
    PostTaskToWorkerThread( 
        [input]() 
        { 
            Output output = CalculateOutput(input);
            PostTaskToMainUI( 
                [output]() 
                {
                    UpdateUI(output);
                });
        }); 
}

Note the following: