Description: c++ con in act 2

From C++ Concurrency in Action, Second Edition by Anthony Williams

This article talks about how you can improve the responsiveness by separating concerns with concurrency.


Save 37% off C++ Concurrency in Action, Second Edition. Just enter fccwilliams into the discount code box at checkout at manning.com.


Most modern graphical user interface frameworks are event driven; the user performs actions on the user interface by pressing keys or moving the mouse, which generate a series of events or messages that the application then handles. The system may also generate messages or events on its own. In order to ensure that all events and messages are correctly handled, the application typically has an event loop that looks like this:

  
 while(true)
 {
     event_data event=get_event();
     if(event.type==quit)
         break;
     process(event);
 }
  

Obviously, the details of the API vary, but the structure is generally the same: wait for an event, do whatever processing is necessary to handle it, and then wait for the next one. If you have a single-threaded application, this can make long-running tasks hard to write (as I describe in chapter 8 of the book). In order to ensure that user input is handled in a timely manner, get_event() and process() must be called with reasonable frequency, whatever the application is doing. This means that either the task must periodically suspend itself and return control to the event loop, or the get_event()/process() code must be called from within the code at convenient points. Either option complicates the implementation of the task.

By separating the concerns with concurrency, you can put the lengthy task on a whole new thread and leave a dedicated GUI thread to process the events. The threads can then communicate through simple mechanisms rather than having to somehow mix the event-handling code in with the task code. The following listing shows a simple outline for this separation.

Listing 1 Separating GUI thread from task thread

  
 std::thread task_thread;
 std::atomic<bool> task_cancelled(false);
 void gui_thread()
 {
     while(true)
     {
         event_data event=get_event();
         if(event.type==quit)
             break;
         process(event);
     }
 }
 void task()
 {
     while(!task_complete() && !task_cancelled)
     {
         do_next_operation();
     }
     if(task_cancelled)
     {
         perform_cleanup();
     }
     else
     {
         post_gui_event(task_complete);
     }
 }
 void process(event_data const& event)
 {
     switch(event.type)
     {
     case start_task:
         task_cancelled=false;
         task_thread=std::thread(task);
         break;
     case stop_task:
         task_cancelled=true;
         task_thread.join();
         break;
     case task_complete:
         task_thread.join();
         display_results();
         break;
     default:
         //...
     }
 }
  

By separating the concerns in this way, the user thread is always able to respond to the events in a timely fashion, even if the task takes a long time. This responsiveness is often key to the user experience when using an application; applications that completely lockup whenever a particular operation is performed (whatever that may be) are inconvenient to use. By providing a dedicated event-handling thread, the GUI can handle GUI-specific messages (such as resizing or repainting the window) without interrupting the execution of the time-consuming processing, while still passing on the relevant messages where they affect the long-running task.

That’s all for this article.


If you want to learn more about the book, check out the first chapter on liveBook for free here and see this slide deck.