QGIS API Documentation 4.1.0-Master (60fea48833c)
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
201{
202 public:
211 : mObject( object )
212 {
213 Q_ASSERT_X( mObject->thread() == nullptr || mObject->thread() == QThread::currentThread(), "QgsScopedAssignObjectToCurrentThread", "QObject was already assigned to a different thread!" );
214 if ( mObject->thread() != QThread::currentThread() )
215 mObject->moveToThread( QThread::currentThread() );
216 }
217
218 ~QgsScopedAssignObjectToCurrentThread() { mObject->moveToThread( nullptr ); }
219
222
223 private:
224 QObject *mObject = nullptr;
225};
226
233class CORE_EXPORT QgsThreadingUtils
234{
235 public:
252 template<typename Func> static bool runOnMainThread( const Func &func, QgsFeedback *feedback = nullptr )
253 {
254 // Make sure we only deal with the vector layer on the main thread where it lives.
255 // Anything else risks a crash.
256 if ( QThread::currentThread() == qApp->thread() )
257 {
258 func();
259 return true;
260 }
261 else
262 {
263 if ( feedback )
264 {
265 // This semaphore will block the worker thread until the main thread is ready.
266 // Ready means the event to execute the waitFunc has arrived in the event loop
267 // and is being executed.
268 QSemaphore semaphoreMainThreadReady( 1 );
269
270 // This semaphore will block the main thread until the worker thread is ready.
271 // Once the main thread is executing the waitFunc, it will wait for this semaphore
272 // to be released. This way we can make sure that
273 QSemaphore semaphoreWorkerThreadReady( 1 );
274
275 // Acquire both semaphores. We want the main thread and the current thread to be blocked
276 // until it's safe to continue.
277 semaphoreMainThreadReady.acquire();
278 semaphoreWorkerThreadReady.acquire();
279
280 const std::function<void()> waitFunc = [&semaphoreMainThreadReady, &semaphoreWorkerThreadReady]() {
281 // This function is executed on the main thread. As soon as it's executed
282 // it will tell the worker thread that the main thread is blocked by releasing
283 // the semaphore.
284 semaphoreMainThreadReady.release();
285
286 // ... and wait for the worker thread to release its semaphore
287 semaphoreWorkerThreadReady.acquire();
288 };
289
290 QMetaObject::invokeMethod( qApp, waitFunc, Qt::QueuedConnection );
291
292 // while we are in the event queue for the main thread and not yet
293 // being executed, check all 100 ms if the feedback is canceled.
294 while ( !semaphoreMainThreadReady.tryAcquire( 1, 100 ) )
295 {
296 if ( feedback->isCanceled() )
297 {
298 semaphoreWorkerThreadReady.release();
299 return false;
300 }
301 }
302
303 // finally, the main thread is blocked and we are (most likely) not canceled.
304 // let's do the real work!!
305 func();
306
307 // work done -> tell the main thread he may continue
308 semaphoreWorkerThreadReady.release();
309 return true;
310 }
311 QMetaObject::invokeMethod( qApp, func, Qt::BlockingQueuedConnection );
312 return true;
313 }
314 }
315#if defined( QGISDEBUG )
317 static QSet< QString > sEmittedWarnings;
319 static QMutex sEmittedWarningMutex;
320#endif
321};
322
323
337{
338 public:
342 QgsScopedThreadName( const QString &name )
343 {
344#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
345 mOldName = getCurrentThreadName();
346 setCurrentThreadName( name );
347#else
348 ( void ) name;
349#endif
350 }
351
356 {
357#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
358 setCurrentThreadName( mOldName );
359#endif
360 }
361
362 private:
363#if ( defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE ) ) || defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
364 QString mOldName;
365
366 static QString getCurrentThreadName()
367 {
368#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
369 char name[16];
370 prctl( PR_GET_NAME, name, 0, 0, 0 );
371 return QString( name );
372#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
373 char name[16];
374 pthread_get_name_np( pthread_self(), name, sizeof( name ) );
375 return QString( name );
376#else
377 return QString();
378#endif
379 }
380
381 static void setCurrentThreadName( const QString &name )
382 {
383#if defined( Q_OS_LINUX ) && !defined( QT_LINUXBASE )
384 prctl( PR_SET_NAME, name.toLocal8Bit().constData(), 0, 0, 0 );
385#elif defined( Q_OS_FREEBSD ) || defined( Q_OS_OPENBSD )
386 pthread_set_name_np( pthread_self(), name.toLocal8Bit().constData() );
387#else
388 ( void ) name;
389#endif
390 }
391#endif
392};
393
394
395#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.