QGIS API Documentation 3.39.0-Master (bca3cdb6021)
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 "qgis_core.h"
22#include "qgsconfig.h"
23
24#include "qgsfeedback.h"
25
26#include <QThread>
27#if defined(QGISDEBUG) || defined(AGGRESSIVE_SAFE_MODE)
28#include <QDebug>
29#include <QMutex>
30#endif
31#include <QSemaphore>
32#include <QCoreApplication>
33#include <memory>
34
35#ifdef __clang_analyzer__
36#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS do {} while(false);
37#elif defined(AGGRESSIVE_SAFE_MODE)
38#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS if ( QThread::currentThread() != thread() ) {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() ); }
39#elif defined(QGISDEBUG)
40#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS if ( QThread::currentThread() != thread() ) {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(); }
41#else
42#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS do {} while(false);
43#endif
44
45// !!DO NOT USE THIS FOR NEW CODE !!
46// This is in place to keep legacy code running and should be removed in the future.
47#ifdef __clang_analyzer__
48#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL do {} while(false);
49#elif defined(QGISDEBUG)
50#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL if ( QThread::currentThread() != thread() ) { const QString location = QStringLiteral("%1 (%2:%3)").arg( QString( __FUNCTION__ ) ,QString( __FILE__ ), QString::number( __LINE__ ) ); QgsThreadingUtils::sEmittedWarningMutex.lock(); if ( !QgsThreadingUtils::sEmittedWarnings.contains( location ) ) { 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(); QgsThreadingUtils::sEmittedWarnings.insert( location ); } QgsThreadingUtils::sEmittedWarningMutex.unlock(); }
51#else
52#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL do {} while(false);
53#endif
54
55#ifdef __clang_analyzer__
56#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY(other) do {} while(false);(void)other;
57#elif defined(AGGRESSIVE_SAFE_MODE)
58#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY(other) if ( other->thread() != thread() ) {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() ); }
59#elif defined(QGISDEBUG)
60#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY(other) if ( other->thread() != thread() ) {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(); }
61#else
62#define QGIS_CHECK_QOBJECT_THREAD_EQUALITY(other) do {} while(false);(void)other;
63#endif
64
65#ifdef __clang_analyzer__
66#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS(other) do {} while(false);(void)other;
67#elif defined(AGGRESSIVE_SAFE_MODE)
68#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS(other) if ( other->thread() != QThread::currentThread() ) {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() ); }
69#elif defined(QGISDEBUG)
70#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS(other) if ( other->thread() != QThread::currentThread() ) {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(); }
71#else
72#define QGIS_CHECK_OTHER_QOBJECT_THREAD_ACCESS(other) do {} while(false);(void)other;
73#endif
74
75
83{
84 public:
85
94 : mObject( object )
95 {
96 Q_ASSERT_X( mObject->thread() == nullptr || mObject->thread() == QThread::currentThread(), "QgsScopedAssignObjectToCurrentThread", "QObject was already assigned to a different thread!" );
97 if ( mObject->thread() != QThread::currentThread() )
98 mObject->moveToThread( QThread::currentThread() );
99 }
100
102 {
103 mObject->moveToThread( nullptr );
104 }
105
108
109 private:
110 QObject *mObject = nullptr;
111};
112
119class CORE_EXPORT QgsThreadingUtils
120{
121 public:
122
139 template <typename Func>
140 static bool runOnMainThread( const Func &func, QgsFeedback *feedback = nullptr )
141 {
142 // Make sure we only deal with the vector layer on the main thread where it lives.
143 // Anything else risks a crash.
144 if ( QThread::currentThread() == qApp->thread() )
145 {
146 func();
147 return true;
148 }
149 else
150 {
151 if ( feedback )
152 {
153 // This semaphore will block the worker thread until the main thread is ready.
154 // Ready means the event to execute the waitFunc has arrived in the event loop
155 // and is being executed.
156 QSemaphore semaphoreMainThreadReady( 1 );
157
158 // This semaphore will block the main thread until the worker thread is ready.
159 // Once the main thread is executing the waitFunc, it will wait for this semaphore
160 // to be released. This way we can make sure that
161 QSemaphore semaphoreWorkerThreadReady( 1 );
162
163 // Acquire both semaphores. We want the main thread and the current thread to be blocked
164 // until it's safe to continue.
165 semaphoreMainThreadReady.acquire();
166 semaphoreWorkerThreadReady.acquire();
167
168 const std::function<void()> waitFunc = [&semaphoreMainThreadReady, &semaphoreWorkerThreadReady]()
169 {
170 // This function is executed on the main thread. As soon as it's executed
171 // it will tell the worker thread that the main thread is blocked by releasing
172 // the semaphore.
173 semaphoreMainThreadReady.release();
174
175 // ... and wait for the worker thread to release its semaphore
176 semaphoreWorkerThreadReady.acquire();
177 };
178
179 QMetaObject::invokeMethod( qApp, waitFunc, Qt::QueuedConnection );
180
181 // while we are in the event queue for the main thread and not yet
182 // being executed, check all 100 ms if the feedback is canceled.
183 while ( !semaphoreMainThreadReady.tryAcquire( 1, 100 ) )
184 {
185 if ( feedback->isCanceled() )
186 {
187 semaphoreWorkerThreadReady.release();
188 return false;
189 }
190 }
191
192 // finally, the main thread is blocked and we are (most likely) not canceled.
193 // let's do the real work!!
194 func();
195
196 // work done -> tell the main thread he may continue
197 semaphoreWorkerThreadReady.release();
198 return true;
199 }
200 QMetaObject::invokeMethod( qApp, func, Qt::BlockingQueuedConnection );
201 return true;
202 }
203 }
204#if defined(QGISDEBUG)
206 static QSet< QString > sEmittedWarnings;
208 static QMutex sEmittedWarningMutex;
209#endif
210
211};
212
213
214#endif
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
Temporarily moves a QObject to the current thread, then resets it back to nullptr thread on destructi...
QgsScopedAssignObjectToCurrentThread(QObject *object)
Assigns object to the current thread.
QgsScopedAssignObjectToCurrentThread & operator=(const QgsScopedAssignObjectToCurrentThread &)=delete
QgsScopedAssignObjectToCurrentThread(const QgsScopedAssignObjectToCurrentThread &other)=delete
Provides threading utilities for QGIS.
static bool runOnMainThread(const Func &func, QgsFeedback *feedback=nullptr)
Guarantees that func is executed on the main thread.