C++0x: Running code in GUI thread from worker threads.

January 29, 2011

Filed under: How-to,HTMLayout,Sciter,Source code — Andrew @ 11:53 pm

One of topics in design of multi-threading GUI applications is to choose method of calling GUI code from so called worker threads – threads that do some work in background. At some point they need to report results to the GUI. But GUI is a shareable resource so some form of synchronization is required. One approach is to use some global lock/mutex and capture it each time when any thread need to access tree of GUI objects.

Another method is to enqueue update tasks to the GUI thread. GUI thread will execute them when it can. This approach does not require synchronization code to be spread across whole application. The whole system will work faster and probability of deadlocks/synchronization issues is almost zero in this case.

The only problem: for each action that you need to do in GUI thread you will need to create separate task/object for deferred execution (or to use some other mechanism like marshalling in COM).

With new features of C++0x we can accomplish this with almost zero overhead. In particular we will need lambdas.

Here is how our code of worker thread function may look like:

void worker_thread()
{
  dom::element some_el = ...; 
  string html_to_set = ...;
  int result;
  ...
  auto gui_code_block = [some_el,html_to_set,&result] () 
  { // executed in GUI thread
    if(some.children() > 10)
      some.set_html(html_to_set);
    result = 20; // report some result if needed.
  };
  gui_exec(gui_code_block); 
  ...
  if( result == 20 ) ...;
  ...
}

As you see here I am declaring inline code block that will be
executed in GUI thread while the rest of the function body will run in its own thread.

The key point here is the gui_exec() function that may look like as:

void gui_exec( std::function<void()> gui_block )
{
  event evt;
  PostMessage(hwnd, WM_NULL, WPARAM(&evt),LPARAM(&gui_block));
  evt.wait(); // suspend worker thread until GUI will execute the block.
}

It posts the message to the GUI with address of synchronization object and
address of our GUI code block (function in fact). It is simple as you see.

And the last piece – we need message handler in GUI thread that will actually execute that code block. The best place for it is inside so called “GUI message pump” – Get/DispatchMessage loop that exist in any GUI application:

typedef std::function<void(void)> gui_block;

while (GetMessage(&msg, NULL, 0, 0))
{
    if( msg.message == WM_NULL )
    {
      gui_block* pf = reinterpret_cast<gui_block*>(msg.lParam);
      event* pe = reinterpret_cast<event*>(msg.wParam);
      (*pf)();  // execute the block
      pe->signal(); // signal that we've done with it
                        // this will resume execution of worker thread.
    }
    else 
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

And that is it.

The only note: I am using WM_NULL message here but in reality you should use some other not that popular message for it. RegisterWindowMessage() will help to get that message number.

3 Comments

  1. Great explanation, thank you very much for this pieces of information and pieces of code.

    Guillaume R. (http://www.sd-informatique.com)

    Comment by Guillaume — February 2, 2011 @ 2:25 am

  2. Hi,

    Great post.
    Just one question. Instead of
    event evt;
    PostMessage(hwnd, WM_NULL, WPARAM(&evt),LPARAM(&gui_block));
    evt.wait(); // suspend worker thread until GUI will execute the block.
    wouldn’t it be simpler to use SendMessage so that the thread will wait until the message is processed?

    Thanks.

    Comment by Andrei — April 7, 2011 @ 11:40 pm

  3. Andrei is absolutly correct, you must use SendMessage instead of PostMessage, trust me, I learned it by the hard way where some events where not reaching my UI thread. It will occur whenever your UI thread is inside a inner system-modal-loop (ex.: when you are resizing the window)

    Comment by midiway — November 4, 2012 @ 5:52 pm

RSS feed for comments on this post. TrackBack URI

Sorry, the comment form is closed at this time.