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