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