QGIS API Documentation 3.99.0-Master (a8882ad4560)
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#define SIP_NO_FILE
20
21#include "qgsconfig.h"
22
23#include "qgis_core.h"
24#include "qgsfeedback.h"
25
26#include <QThread>
27
28#if defined( QGISDEBUG ) || defined( AGGRESSIVE_SAFE_MODE )
29#include <QDebug>
30#include <QMutex>
31#endif
32#include <QSemaphore>
33#include <QCoreApplication>
34#include <memory>
35
36
37#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
38#include <sys/prctl.h>
39#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
40#include <pthread.h>
41#include <pthread_np.h>
42#endif
43
44#ifdef __clang_analyzer__
45#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
46 do \
47 { \
48 } while ( false );
49#elif defined( AGGRESSIVE_SAFE_MODE )
50#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
51 if ( QThread::currentThread() != thread() ) \
52 { \
53 qFatal( "%s", 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() ).arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ).arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ).toLocal8Bit().constData() ); \
54 }
55#elif defined( QGISDEBUG )
56#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
57 if ( QThread::currentThread() != thread() ) \
58 { \
59 qWarning() << 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() ).arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ).arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ).toLocal8Bit().constData(); \
60 }
61#else
62#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS \
63 do \
64 { \
65 } while ( false );
66#endif
67
68// !!DO NOT USE THIS FOR NEW CODE !!
69// This is in place to keep legacy code running and should be removed in the future.
70#ifdef __clang_analyzer__
71#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
72 do \
73 { \
74 } while ( false );
75#elif defined( QGISDEBUG )
76#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
77 if ( QThread::currentThread() != thread() ) \
78 { \
79 const QString location = u"%1 (%2:%3)"_s.arg( QString( __FUNCTION__ ), QString( __FILE__ ), QString::number( __LINE__ ) ); \
80 QgsThreadingUtils::sEmittedWarningMutex.lock(); \
81 if ( !QgsThreadingUtils::sEmittedWarnings.contains( location ) ) \
82 { \
83 qWarning() << u"%1 is run from a different thread than the object '%2' lives in [0x%3 vs 0x%4]"_s.arg( location, objectName() ).arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ).arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ).toLocal8Bit().constData(); \
84 QgsThreadingUtils::sEmittedWarnings.insert( location ); \
85 } \
86 QgsThreadingUtils::sEmittedWarningMutex.unlock(); \
87 }
88#else
89#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL \
90 do \
91 { \
92 } while ( false );
93#endif
94
95#ifdef __clang_analyzer__
96#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
97 do \
98 { \
99 } while ( false ); \
100 ( void ) ( other );
101#elif defined( AGGRESSIVE_SAFE_MODE )
102#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
103 if ( ( other )->thread() != thread() ) \
104 { \
105 qFatal( "%s", u"%2 (%1:%3) Object %4 is from a different thread than the object %5 lives in [0x%6 vs 0x%7]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName(), objectName() ).arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ).arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ).toLocal8Bit().constData() ); \
106 }
107#elif defined( QGISDEBUG )
108#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
109 if ( ( other )->thread() != thread() ) \
110 { \
111 qWarning() << u"%2 (%1:%3) Object %4 is from a different thread than the object %5 lives in [0x%6 vs 0x%7]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName(), objectName() ).arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ).arg( reinterpret_cast< qint64 >( thread() ), 0, 16 ).toLocal8Bit().constData(); \
112 }
113#else
114#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY( other ) \
115 do \
116 { \
117 } while ( false ); \
118 ( void ) ( other );
119#endif
120
121#ifdef __clang_analyzer__
122#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
123 do \
124 { \
125 } while ( false ); \
126 ( void ) ( other );
127#elif defined( AGGRESSIVE_SAFE_MODE )
128#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
129 if ( ( other )->thread() != QThread::currentThread() ) \
130 { \
131 qFatal( "%s", u"%2 (%1:%3) Access from a different thread than the object %4 lives in [0x%5 vs 0x%6]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName() ).arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ).arg( reinterpret_cast< qint64 >( ( other )->thread() ), 0, 16 ).toLocal8Bit().constData() ); \
132 }
133#elif defined( QGISDEBUG )
134#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
135 if ( ( other )->thread() != QThread::currentThread() ) \
136 { \
137 qWarning() << u"%2 (%1:%3) Access from a different thread than the object %4 lives in [0x%5 vs 0x%6]"_s.arg( QString( __FILE__ ), QString( __FUNCTION__ ), QString::number( __LINE__ ), ( other )->objectName() ).arg( reinterpret_cast< qint64 >( QThread::currentThread() ), 0, 16 ).arg( reinterpret_cast< qint64 >( ( other )->thread() ), 0, 16 ).toLocal8Bit().constData(); \
138 }
139#else
140#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS( other ) \
141 do \
142 { \
143 } while ( false ); \
144 ( void ) ( other );
145#endif
146
147
155{
156 public:
165 : mObject( object )
166 {
167 Q_ASSERT_X( mObject->thread() == nullptr || mObject->thread() == QThread::currentThread(), "QgsScopedAssignObjectToCurrentThread", "QObject was already assigned to a different thread!" );
168 if ( mObject->thread() != QThread::currentThread() )
169 mObject->moveToThread( QThread::currentThread() );
170 }
171
173 {
174 mObject->moveToThread( nullptr );
175 }
176
179
180 private:
181 QObject *mObject = nullptr;
182};
183
190class CORE_EXPORT QgsThreadingUtils
191{
192 public:
209 template<typename Func>
210 static bool runOnMainThread( const Func &func, QgsFeedback *feedback = nullptr )
211 {
212 // Make sure we only deal with the vector layer on the main thread where it lives.
213 // Anything else risks a crash.
214 if ( QThread::currentThread() == qApp->thread() )
215 {
216 func();
217 return true;
218 }
219 else
220 {
221 if ( feedback )
222 {
223 // This semaphore will block the worker thread until the main thread is ready.
224 // Ready means the event to execute the waitFunc has arrived in the event loop
225 // and is being executed.
226 QSemaphore semaphoreMainThreadReady( 1 );
227
228 // This semaphore will block the main thread until the worker thread is ready.
229 // Once the main thread is executing the waitFunc, it will wait for this semaphore
230 // to be released. This way we can make sure that
231 QSemaphore semaphoreWorkerThreadReady( 1 );
232
233 // Acquire both semaphores. We want the main thread and the current thread to be blocked
234 // until it's safe to continue.
235 semaphoreMainThreadReady.acquire();
236 semaphoreWorkerThreadReady.acquire();
237
238 const std::function<void()> waitFunc = [&semaphoreMainThreadReady, &semaphoreWorkerThreadReady]()
239 {
240 // This function is executed on the main thread. As soon as it's executed
241 // it will tell the worker thread that the main thread is blocked by releasing
242 // the semaphore.
243 semaphoreMainThreadReady.release();
244
245 // ... and wait for the worker thread to release its semaphore
246 semaphoreWorkerThreadReady.acquire();
247 };
248
249 QMetaObject::invokeMethod( qApp, waitFunc, Qt::QueuedConnection );
250
251 // while we are in the event queue for the main thread and not yet
252 // being executed, check all 100 ms if the feedback is canceled.
253 while ( !semaphoreMainThreadReady.tryAcquire( 1, 100 ) )
254 {
255 if ( feedback->isCanceled() )
256 {
257 semaphoreWorkerThreadReady.release();
258 return false;
259 }
260 }
261
262 // finally, the main thread is blocked and we are (most likely) not canceled.
263 // let's do the real work!!
264 func();
265
266 // work done -> tell the main thread he may continue
267 semaphoreWorkerThreadReady.release();
268 return true;
269 }
270 QMetaObject::invokeMethod( qApp, func, Qt::BlockingQueuedConnection );
271 return true;
272 }
273 }
274#if defined( QGISDEBUG )
276 static QSet< QString > sEmittedWarnings;
278 static QMutex sEmittedWarningMutex;
279#endif
280};
281
282
296{
297 public:
301 QgsScopedThreadName( const QString &name )
302 {
303#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
304 mOldName = getCurrentThreadName();
305 setCurrentThreadName( name );
306#else
307 ( void ) name;
308#endif
309 }
310
315 {
316#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
317 setCurrentThreadName( mOldName );
318#endif
319 }
320
321 private:
322#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
323 QString mOldName;
324
325 static QString getCurrentThreadName()
326 {
327#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
328 char name[16];
329 prctl( PR_GET_NAME, name, 0, 0, 0 );
330 return QString( name );
331#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
332 char name[16];
333 pthread_get_name_np( pthread_self(), name, sizeof( name ) );
334 return QString( name );
335#else
336 return QString();
337#endif
338 }
339
340 static void setCurrentThreadName( const QString &name )
341 {
342#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
343 prctl( PR_SET_NAME, name.toLocal8Bit().constData(), 0, 0, 0 );
344#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
345 pthread_set_name_np( pthread_self(), name.toLocal8Bit().constData() );
346#else
347 ( void ) name;
348#endif
349 }
350#endif
351};
352
353
354#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.