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