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