QGIS API Documentation 4.1.0-Master (467af3bbe65)
Loading...
Searching...
No Matches
qgsthreadingutils.h
Go to the documentation of this file.
1/***************************************************************************
2 qgsthreadingutils.h
3 --------------------------------------
4 Date : 11.9.2018
5 Copyright : (C) 2018 by Matthias Kuhn
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#ifndef QGSTHREADINGUTILS_H
17#define QGSTHREADINGUTILS_H
18
19
20#include "qgsconfig.h"
21
22#include "qgis_core.h"
23#include "qgsfeedback.h"
24
25#include <QString>
26#include <QThread>
27
28#define SIP_NO_FILE
29
30using namespace Qt::StringLiterals;
31
32#if defined( QGISDEBUG ) || defined( AGGRESSIVE_SAFE_MODE )
33#include <QDebug>
34#include <QMutex>
35#endif
36#include <QSemaphore>
37#include <QCoreApplication>
38#include <memory>
39
40
41#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
42#include <sys/prctl.h>
43#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
44#include <pthread.h>
45#include <pthread_np.h>
46#endif
47
48#ifdef __clang_analyzer__
49#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
50 do \
51 { \
52 } while ( false );
53#elif defined( AGGRESSIVE_SAFE_MODE )
54#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
55 if ( QThread::currentThread() != thread() ) \
56 { \
57 qFatal( \
58 "%s", \
59 u"%2 (%1:%3) is run from a different thread than the object '%4' lives in [0x%5 vs 0x%6]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), objectName() ) \
60 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
61 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
62 .toLocal8Bit() \
63 .constData() \
64 ); \
65 }
66#elif defined( QGISDEBUG )
67#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
68 if ( QThread::currentThread() != thread() ) \
69 { \
70 qWarning() << u"%2 (%1:%3) is run from a different thread than the object '%4' lives in [0x%5 vs 0x%6]"_s \
71 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), objectName() ) \
72 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
73 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
74 .toLocal8Bit() \
75 .constData(); \
76 }
77#else
78#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
79 do \
80 { \
81 } while ( false );
82#endif
83
84// !!DO NOT USE THIS FOR NEW CODE !!
85// This is in place to keep legacy code running and should be removed in the future.
86#ifdef __clang_analyzer__
87#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
88 do \
89 { \
90 } while ( false );
91#elif defined( QGISDEBUG )
92#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
93 if ( QThread::currentThread() != thread() ) \
94 { \
95 const QString location = u"%1 (%2:%3)"_s.arg( QString( __FUNCTION__ ), QString( __FILE__ ), QString::number( __LINE__ ) ); \
96 QgsThreadingUtils::sEmittedWarningMutex.lock(); \
97 if ( !QgsThreadingUtils::sEmittedWarnings.contains( location ) ) \
98 { \
99 qWarning() << u"%1 is run from a different thread than the object '%2' lives in [0x%3 vs 0x%4]"_s.arg( location, objectName() ) \
100 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
101 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
102 .toLocal8Bit() \
103 .constData(); \
104 QgsThreadingUtils::sEmittedWarnings.insert( location ); \
105 } \
106 QgsThreadingUtils::sEmittedWarningMutex.unlock(); \
107 }
108#else
109#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
110 do \
111 { \
112 } while ( false );
113#endif
114
115#ifdef __clang_analyzer__
116#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
117 do \
118 { \
119 } while ( false ); \
120 ( void ) ( other );
121#elif defined( AGGRESSIVE_SAFE_MODE )
122#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
123 if ( ( other )->thread() != thread() ) \
124 { \
125 qFatal( \
126 "%s", \
127 u"%2 (%1:%3) Object %4 is from a different thread than the object %5 lives in [0x%6 vs 0x%7]"_s \
128 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName(), objectName() ) \
129 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
130 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
131 .toLocal8Bit() \
132 .constData() \
133 ); \
134 }
135#elif defined( QGISDEBUG )
136#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
137 if ( ( other )->thread() != thread() ) \
138 { \
139 qWarning() << u"%2 (%1:%3) Object %4 is from a different thread than the object %5 lives in [0x%6 vs 0x%7]"_s \
140 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName(), objectName() ) \
141 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
142 .arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ) \
143 .toLocal8Bit() \
144 .constData(); \
145 }
146#else
147#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
148 do \
149 { \
150 } while ( false ); \
151 ( void ) ( other );
152#endif
153
154#ifdef __clang_analyzer__
155#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
156 do \
157 { \
158 } while ( false ); \
159 ( void ) ( other );
160#elif defined( AGGRESSIVE_SAFE_MODE )
161#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
162 if ( ( other )->thread() != QThread::currentThread() ) \
163 { \
164 qFatal( \
165 "%s", \
166 u"%2 (%1:%3) Access from a different thread than the object %4 lives in [0x%5 vs 0x%6]"_s \
167 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName() ) \
168 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
169 .arg( reinterpret_cast< qint64 >( ( other )->thread() ), 0, 16 ) \
170 .toLocal8Bit() \
171 .constData() \
172 ); \
173 }
174#elif defined( QGISDEBUG )
175#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
176 if ( ( other )->thread() != QThread::currentThread() ) \
177 { \
178 qWarning() << u"%2 (%1:%3) Access from a different thread than the object %4 lives in [0x%5 vs 0x%6]"_s \
179 .arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName() ) \
180 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
181 .arg( reinterpret_cast< qint64 >( ( other )->thread() ), 0, 16 ) \
182 .toLocal8Bit() \
183 .constData(); \
184 }
185#else
186#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
187 do \
188 { \
189 } while ( false ); \
190 ( void ) ( other );
191#endif
192
193#ifdef __clang_analyzer__
194#define QGIS_CHECK_MAIN_THREAD_ACCESS \
195 do \
196 { \
197 } while ( false );
198#elif defined( AGGRESSIVE_SAFE_MODE )
199#define QGIS_CHECK_MAIN_THREAD_ACCESS \
200 if ( QThread::currentThread() != QCoreApplication::instance()->thread() ) \
201 { \
202 qFatal( \
203 "%s", \
204 u"%2 (%1:%3) must be called from the main thread [current: 0x%4, main: 0x%5]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ) ) \
205 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
206 .arg( reinterpret_cast< qint64 >( QCoreApplication::instance()->thread() ), 0, 16 ) \
207 .toLocal8Bit() \
208 .constData() \
209 ); \
210 }
211#elif defined( QGISDEBUG )
212#define QGIS_CHECK_MAIN_THREAD_ACCESS \
213 if ( QThread::currentThread() != QCoreApplication::instance()->thread() ) \
214 { \
215 qWarning() << u"%2 (%1:%3) must be called from the main thread [current: 0x%4, main: 0x%5]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ) ) \
216 .arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ) \
217 .arg( reinterpret_cast< qint64 >( QCoreApplication::instance()->thread() ), 0, 16 ) \
218 .toLocal8Bit() \
219 .constData(); \
220 }
221#else
222#define QGIS_CHECK_MAIN_THREAD_ACCESS \
223 do \
224 { \
225 } while ( false );
226#endif
227
228
236{
237 public:
246 : mObject( object )
247 {
248 Q_ASSERT_X( mObject->thread() == nullptr || mObject->thread() == QThread::currentThread(), "QgsScopedAssignObjectToCurrentThread", "QObject was already assigned to a different thread!" );
249 if ( mObject->thread() != QThread::currentThread() )
250 mObject->moveToThread( QThread::currentThread() );
251 }
252
253 ~QgsScopedAssignObjectToCurrentThread() { mObject->moveToThread( nullptr ); }
254
257
258 private:
259 QObject *mObject = nullptr;
260};
261
268class CORE_EXPORT QgsThreadingUtils
269{
270 public:
287 template<typename Func> static bool runOnMainThread( const Func &func, QgsFeedback *feedback = nullptr )
288 {
289 // Make sure we only deal with the vector layer on the main thread where it lives.
290 // Anything else risks a crash.
291 if ( QThread::currentThread() == qApp->thread() )
292 {
293 func();
294 return true;
295 }
296 else
297 {
298 if ( feedback )
299 {
300 // This semaphore will block the worker thread until the main thread is ready.
301 // Ready means the event to execute the waitFunc has arrived in the event loop
302 // and is being executed.
303 QSemaphore semaphoreMainThreadReady( 1 );
304
305 // This semaphore will block the main thread until the worker thread is ready.
306 // Once the main thread is executing the waitFunc, it will wait for this semaphore
307 // to be released. This way we can make sure that
308 QSemaphore semaphoreWorkerThreadReady( 1 );
309
310 // Acquire both semaphores. We want the main thread and the current thread to be blocked
311 // until it's safe to continue.
312 semaphoreMainThreadReady.acquire();
313 semaphoreWorkerThreadReady.acquire();
314
315 const std::function<void()> waitFunc = [&semaphoreMainThreadReady, &semaphoreWorkerThreadReady]() {
316 // This function is executed on the main thread. As soon as it's executed
317 // it will tell the worker thread that the main thread is blocked by releasing
318 // the semaphore.
319 semaphoreMainThreadReady.release();
320
321 // ... and wait for the worker thread to release its semaphore
322 semaphoreWorkerThreadReady.acquire();
323 };
324
325 QMetaObject::invokeMethod( qApp, waitFunc, Qt::QueuedConnection );
326
327 // while we are in the event queue for the main thread and not yet
328 // being executed, check all 100 ms if the feedback is canceled.
329 while ( !semaphoreMainThreadReady.tryAcquire( 1, 100 ) )
330 {
331 if ( feedback->isCanceled() )
332 {
333 semaphoreWorkerThreadReady.release();
334 return false;
335 }
336 }
337
338 // finally, the main thread is blocked and we are (most likely) not canceled.
339 // let's do the real work!!
340 func();
341
342 // work done -> tell the main thread he may continue
343 semaphoreWorkerThreadReady.release();
344 return true;
345 }
346 QMetaObject::invokeMethod( qApp, func, Qt::BlockingQueuedConnection );
347 return true;
348 }
349 }
350#if defined( QGISDEBUG )
352 static QSet< QString > sEmittedWarnings;
354 static QMutex sEmittedWarningMutex;
355#endif
356
362 static QString threadDescription( QThread *thread );
363};
364
365
379{
380 public:
384 QgsScopedThreadName( const QString &name )
385 {
386#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
387 mOldName = getCurrentThreadName();
388 setCurrentThreadName( name );
389#else
390 ( void ) name;
391#endif
392 }
393
398 {
399#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
400 setCurrentThreadName( mOldName );
401#endif
402 }
403
404 private:
405#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
406 QString mOldName;
407
408 static QString getCurrentThreadName()
409 {
410#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
411 char name[16];
412 prctl( PR_GET_NAME, name, 0, 0, 0 );
413 return QString( name );
414#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
415 char name[16];
416 pthread_get_name_np( pthread_self(), name, sizeof( name ) );
417 return QString( name );
418#else
419 return QString();
420#endif
421 }
422
423 static void setCurrentThreadName( const QString &name )
424 {
425#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
426 prctl( PR_SET_NAME, name.toLocal8Bit().constData(), 0, 0, 0 );
427#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
428 pthread_set_name_np( pthread_self(), name.toLocal8Bit().constData() );
429#else
430 ( void ) name;
431#endif
432 }
433#endif
434};
435
436
437#endif
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
QgsScopedAssignObjectToCurrentThread(QObject *object)
Assigns object to the current thread.
QgsScopedAssignObjectToCurrentThread & operator=(const QgsScopedAssignObjectToCurrentThread &)=delete
QgsScopedAssignObjectToCurrentThread(const QgsScopedAssignObjectToCurrentThread &other)=delete
~QgsScopedThreadName()
Restores the thread name back to its original state.
QgsScopedThreadName(const QString &name)
Constructor for QgsScopedThreadName.
Provides threading utilities for QGIS.
static bool runOnMainThread(const Func &func, QgsFeedback *feedback=nullptr)
Guarantees that func is executed on the main thread.