1*67c11716SAlexey Oralov.. _GUI_Thread: 2*67c11716SAlexey Oralov 3*67c11716SAlexey OralovGUI Thread 4*67c11716SAlexey Oralov========== 5*67c11716SAlexey Oralov 6*67c11716SAlexey Oralov.. container:: section 7*67c11716SAlexey Oralov 8*67c11716SAlexey Oralov 9*67c11716SAlexey Oralov .. rubric:: Problem 10*67c11716SAlexey Oralov :class: sectiontitle 11*67c11716SAlexey Oralov 12*67c11716SAlexey Oralov A user interface thread must remain responsive to user requests, and 13*67c11716SAlexey Oralov must not get bogged down in long computations. 14*67c11716SAlexey Oralov 15*67c11716SAlexey Oralov 16*67c11716SAlexey Oralov.. container:: section 17*67c11716SAlexey Oralov 18*67c11716SAlexey Oralov 19*67c11716SAlexey Oralov .. rubric:: Context 20*67c11716SAlexey Oralov :class: sectiontitle 21*67c11716SAlexey Oralov 22*67c11716SAlexey Oralov Graphical user interfaces often have a dedicated thread ("GUI 23*67c11716SAlexey Oralov thread") for servicing user interactions. The thread must remain 24*67c11716SAlexey Oralov responsive to user requests even while the application has long 25*67c11716SAlexey Oralov computations running. For example, the user might want to press a 26*67c11716SAlexey Oralov "cancel" button to stop the long running computation. If the GUI 27*67c11716SAlexey Oralov thread takes part in the long running computation, it will not be 28*67c11716SAlexey Oralov able to respond to user requests. 29*67c11716SAlexey Oralov 30*67c11716SAlexey Oralov 31*67c11716SAlexey Oralov.. container:: section 32*67c11716SAlexey Oralov 33*67c11716SAlexey Oralov 34*67c11716SAlexey Oralov .. rubric:: Forces 35*67c11716SAlexey Oralov :class: sectiontitle 36*67c11716SAlexey Oralov 37*67c11716SAlexey Oralov - The GUI thread services an event loop. 38*67c11716SAlexey Oralov 39*67c11716SAlexey Oralov 40*67c11716SAlexey Oralov - The GUI thread needs to offload work onto other threads without 41*67c11716SAlexey Oralov waiting for the work to complete. 42*67c11716SAlexey Oralov 43*67c11716SAlexey Oralov 44*67c11716SAlexey Oralov - The GUI thread must be responsive to the event loop and not become 45*67c11716SAlexey Oralov dedicated to doing the offloaded work. 46*67c11716SAlexey Oralov 47*67c11716SAlexey Oralov 48*67c11716SAlexey Oralov.. container:: section 49*67c11716SAlexey Oralov 50*67c11716SAlexey Oralov 51*67c11716SAlexey Oralov .. rubric:: Related 52*67c11716SAlexey Oralov :class: sectiontitle 53*67c11716SAlexey Oralov 54*67c11716SAlexey Oralov - Non-Preemptive Priorities 55*67c11716SAlexey Oralov - Local Serializer 56*67c11716SAlexey Oralov 57*67c11716SAlexey Oralov 58*67c11716SAlexey Oralov.. container:: section 59*67c11716SAlexey Oralov 60*67c11716SAlexey Oralov 61*67c11716SAlexey Oralov .. rubric:: Solution 62*67c11716SAlexey Oralov :class: sectiontitle 63*67c11716SAlexey Oralov 64*67c11716SAlexey Oralov The GUI thread offloads the work by firing off a task to do it using 65*67c11716SAlexey Oralov method ``task_arena::enqueue`` of a ``task_arena`` instance. 66*67c11716SAlexey Oralov When finished, the task posts an event to the GUI thread to indicate that the work is done. 67*67c11716SAlexey Oralov The semantics of ``enqueue`` cause the task to eventually run on a worker thread 68*67c11716SAlexey Oralov distinct from the calling thread. 69*67c11716SAlexey Oralov 70*67c11716SAlexey Oralov The following figure sketches the communication paths. Items in black are executed 71*67c11716SAlexey Oralov by the GUI thread; items in blue are executed by another thread. 72*67c11716SAlexey Oralov 73*67c11716SAlexey Oralov |image0| 74*67c11716SAlexey Oralov 75*67c11716SAlexey Oralov.. container:: section 76*67c11716SAlexey Oralov 77*67c11716SAlexey Oralov 78*67c11716SAlexey Oralov .. rubric:: Example 79*67c11716SAlexey Oralov :class: sectiontitle 80*67c11716SAlexey Oralov 81*67c11716SAlexey Oralov The example is for the Microsoft Windows\* operating systems, though 82*67c11716SAlexey Oralov similar principles apply to any GUI using an event loop idiom. For 83*67c11716SAlexey Oralov each event, the GUI thread calls a user-defined function ``WndProc`` to process an event. 84*67c11716SAlexey Oralov 85*67c11716SAlexey Oralov 86*67c11716SAlexey Oralov :: 87*67c11716SAlexey Oralov 88*67c11716SAlexey Oralov 89*67c11716SAlexey Oralov // Event posted from enqueued task when it finishes its work. 90*67c11716SAlexey Oralov const UINT WM_POP_FOO = WM_USER+0; 91*67c11716SAlexey Oralov 92*67c11716SAlexey Oralov 93*67c11716SAlexey Oralov // Queue for transmitting results from enqueued task to GUI thread. 94*67c11716SAlexey Oralov oneapi::tbb::concurrent_queue<Foo>ResultQueue; 95*67c11716SAlexey Oralov 96*67c11716SAlexey Oralov 97*67c11716SAlexey Oralov // GUI thread's private copy of most recently computed result. 98*67c11716SAlexey Oralov Foo CurrentResult; 99*67c11716SAlexey Oralov 100*67c11716SAlexey Oralov 101*67c11716SAlexey Oralov LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { 102*67c11716SAlexey Oralov switch(msg) { 103*67c11716SAlexey Oralov case WM_COMMAND: 104*67c11716SAlexey Oralov switch (LOWORD(wParam)) { 105*67c11716SAlexey Oralov case IDM_LONGRUNNINGWORK: 106*67c11716SAlexey Oralov // User requested a long computation. Delegate it to another thread. 107*67c11716SAlexey Oralov LaunchLongRunningWork(hWnd); 108*67c11716SAlexey Oralov break; 109*67c11716SAlexey Oralov case IDM_EXIT: 110*67c11716SAlexey Oralov DestroyWindow(hWnd); 111*67c11716SAlexey Oralov break; 112*67c11716SAlexey Oralov default: 113*67c11716SAlexey Oralov return DefWindowProc(hWnd, msg, wParam, lParam); 114*67c11716SAlexey Oralov } 115*67c11716SAlexey Oralov break; 116*67c11716SAlexey Oralov case WM_POP_FOO: 117*67c11716SAlexey Oralov // There is another result in ResultQueue for me to grab. 118*67c11716SAlexey Oralov ResultQueue.try_pop(CurrentResult); 119*67c11716SAlexey Oralov // Update the window with the latest result. 120*67c11716SAlexey Oralov RedrawWindow( hWnd, NULL, NULL, RDW_ERASE|RDW_INVALIDATE ); 121*67c11716SAlexey Oralov break; 122*67c11716SAlexey Oralov case WM_PAINT: 123*67c11716SAlexey Oralov Repaint the window using CurrentResult 124*67c11716SAlexey Oralov break; 125*67c11716SAlexey Oralov case WM_DESTROY: 126*67c11716SAlexey Oralov PostQuitMessage(0); 127*67c11716SAlexey Oralov break; 128*67c11716SAlexey Oralov default: 129*67c11716SAlexey Oralov return DefWindowProc( hWnd, msg, wParam, lParam ); 130*67c11716SAlexey Oralov } 131*67c11716SAlexey Oralov return 0; 132*67c11716SAlexey Oralov } 133*67c11716SAlexey Oralov 134*67c11716SAlexey Oralov 135*67c11716SAlexey Oralov The GUI thread processes long computations as follows: 136*67c11716SAlexey Oralov 137*67c11716SAlexey Oralov 138*67c11716SAlexey Oralov #. The GUI thread calls ``LongRunningWork``, which hands off the work 139*67c11716SAlexey Oralov to a worker thread and immediately returns. 140*67c11716SAlexey Oralov 141*67c11716SAlexey Oralov 142*67c11716SAlexey Oralov #. The GUI thread continues servicing the event loop. If it has to 143*67c11716SAlexey Oralov repaint the window, it uses the value of\ ``CurrentResult``, which 144*67c11716SAlexey Oralov is the most recent ``Foo`` that it has seen. 145*67c11716SAlexey Oralov 146*67c11716SAlexey Oralov 147*67c11716SAlexey Oralov When a worker finishes the long computation, it pushes the result 148*67c11716SAlexey Oralov into ResultQueue, and sends a message WM_POP_FOO to the GUI thread. 149*67c11716SAlexey Oralov 150*67c11716SAlexey Oralov 151*67c11716SAlexey Oralov #. The GUI thread services a ``WM_POP_FOO`` message by popping an 152*67c11716SAlexey Oralov item from ResultQueue into CurrentResult. The ``try_pop`` always 153*67c11716SAlexey Oralov succeeds because there is exactly one ``WM_POP_FOO`` message for 154*67c11716SAlexey Oralov each item in ``ResultQueue``. 155*67c11716SAlexey Oralov 156*67c11716SAlexey Oralov 157*67c11716SAlexey Oralov Routine ``LaunchLongRunningWork`` creates a function task and launches it 158*67c11716SAlexey Oralov using method ``task_arena::enqueue``. 159*67c11716SAlexey Oralov 160*67c11716SAlexey Oralov :: 161*67c11716SAlexey Oralov 162*67c11716SAlexey Oralov 163*67c11716SAlexey Oralov class LongTask { 164*67c11716SAlexey Oralov HWND hWnd; 165*67c11716SAlexey Oralov void operator()() { 166*67c11716SAlexey Oralov Do long computation 167*67c11716SAlexey Oralov Foo x = result of long computation 168*67c11716SAlexey Oralov ResultQueue.push( x ); 169*67c11716SAlexey Oralov // Notify GUI thread that result is available. 170*67c11716SAlexey Oralov PostMessage(hWnd,WM_POP_FOO,0,0); 171*67c11716SAlexey Oralov } 172*67c11716SAlexey Oralov public: 173*67c11716SAlexey Oralov LongTask( HWND hWnd_ ) : hWnd(hWnd_) {} 174*67c11716SAlexey Oralov }; 175*67c11716SAlexey Oralov 176*67c11716SAlexey Oralov void LaunchLongRunningWork( HWND hWnd ) { 177*67c11716SAlexey Oralov oneapi::tbb::task_arena a; 178*67c11716SAlexey Oralov a.enqueue(LongTask(hWnd)); 179*67c11716SAlexey Oralov } 180*67c11716SAlexey Oralov 181*67c11716SAlexey Oralov 182*67c11716SAlexey Oralov It is essential to use method ``task_arena::enqueue`` here. 183*67c11716SAlexey Oralov Even though, an explicit ``task_arena`` instance is created, 184*67c11716SAlexey Oralov the method ``enqueue`` ensures that the function task eventually executes when resources permit, 185*67c11716SAlexey Oralov even if no thread explicitly waits on the task. In contrast, ``oneapi::tbb::task_group::run`` may 186*67c11716SAlexey Oralov postpone execution of the function task until it is explicitly waited upon with the ``oneapi::tbb::task_group::wait``. 187*67c11716SAlexey Oralov 188*67c11716SAlexey Oralov The example uses a ``concurrent_queue`` for workers to communicate 189*67c11716SAlexey Oralov results back to the GUI thread. Since only the most recent result 190*67c11716SAlexey Oralov matters in the example, and alternative would be to use a shared 191*67c11716SAlexey Oralov variable protected by a mutex. However, doing so would block the 192*67c11716SAlexey Oralov worker while the GUI thread was holding a lock on the mutex, and vice 193*67c11716SAlexey Oralov versa. Using ``concurrent_queue`` provides a simple robust solution. 194*67c11716SAlexey Oralov 195*67c11716SAlexey Oralov If two long computations are in flight, there is a chance that the 196*67c11716SAlexey Oralov first computation completes after the second one. If displaying the 197*67c11716SAlexey Oralov result of the most recently requested computation is important, then 198*67c11716SAlexey Oralov associate a request serial number with the computation. The GUI 199*67c11716SAlexey Oralov thread can pop from ``ResultQueue`` into a temporary variable, check 200*67c11716SAlexey Oralov the serial number, and update ``CurrentResult`` only if doing so 201*67c11716SAlexey Oralov advances the serial number. 202*67c11716SAlexey Oralov 203*67c11716SAlexey Oralov.. |image0| image:: Images/image007a.jpg 204*67c11716SAlexey Oralov :width: 400px 205*67c11716SAlexey Oralov :height: 150px 206