2020
2121#include " qgis_core.h"
2222
23+ #include " qgsfeedback.h"
24+
2325#include < QThread>
26+ #include < QSemaphore>
27+ #include < memory>
2428
2529/* *
2630 * \ingroup core
@@ -39,24 +43,84 @@ class CORE_EXPORT QgsThreadingUtils
3943 * This is useful to quickly access information from objects that live on the
4044 * main thread and copying this information into worker threads. Avoid running
4145 * expensive code inside \a func.
46+ * If a \a feedback is provided, it will observe if the feedback is canceled.
47+ * In case the feedback is canceled before the main thread started to run the
48+ * function, it will return without executing the function.
4249 *
4350 * \note Only works with Qt >= 5.10, earlier versions will execute the code
4451 * in the worker thread.
4552 *
4653 * \since QGIS 3.4
4754 */
4855 template <typename Func>
49- static void runOnMainThread ( const Func &func )
56+ static bool runOnMainThread ( const Func &func, QgsFeedback *feedback = nullptr )
5057 {
5158#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
5259 // Make sure we only deal with the vector layer on the main thread where it lives.
5360 // Anything else risks a crash.
5461 if ( QThread::currentThread () == qApp->thread () )
62+ {
5563 func ();
64+ return true ;
65+ }
5666 else
67+ {
68+ if ( feedback )
69+ {
70+ // This semaphore will block the worker thread until the main thread is ready.
71+ // Ready means the event to execute the waitFunc has arrived in the event loop
72+ // and is being executed.
73+ QSemaphore semaphoreMainThreadReady ( 1 );
74+
75+ // This semaphore will block the main thread until the worker thread is ready.
76+ // Once the main thread is executing the waitFunc, it will wait for this semaphore
77+ // to be released. This way we can make sure that
78+ QSemaphore semaphoreWorkerThreadReady ( 1 );
79+
80+ // Acquire both semaphores. We want the main thread and the current thread to be blocked
81+ // until it's save to continue.
82+ semaphoreMainThreadReady.acquire ();
83+ semaphoreWorkerThreadReady.acquire ();
84+
85+ std::function<void ()> waitFunc = [&semaphoreMainThreadReady, &semaphoreWorkerThreadReady]()
86+ {
87+ // This function is executed on the main thread. As soon as it's executed
88+ // it will tell the worker thread that the main thread is blocked by releasing
89+ // the semaphore.
90+ semaphoreMainThreadReady.release ();
91+
92+ // ... and wait for the worker thread to release its semaphore
93+ semaphoreWorkerThreadReady.acquire ();
94+ };
95+
96+ QMetaObject::invokeMethod ( qApp, waitFunc, Qt::QueuedConnection );
97+
98+ // while we are in the event queue for the main thread and not yet
99+ // being executed, check all 100 ms if the feedback is canceled.
100+ while ( !semaphoreMainThreadReady.tryAcquire ( 1 , 100 ) )
101+ {
102+ if ( feedback->isCanceled () )
103+ {
104+ semaphoreWorkerThreadReady.release ();
105+ return false ;
106+ }
107+ }
108+
109+ // finally, the main thread is blocked and we are (most likely) not canceled.
110+ // let's do the real work!!
111+ func ();
112+
113+ // work done -> tell the main thread he may continue
114+ semaphoreWorkerThreadReady.release ();
115+ return true ;
116+ }
57117 QMetaObject::invokeMethod ( qApp, func, Qt::BlockingQueuedConnection );
118+ return true ;
119+ }
58120#else
121+ Q_UNUSED ( feedback )
59122 func ();
123+ return true ;
60124#endif
61125 }
62126
0 commit comments