How to Leverage Qt to Make a Qobject Method Thread-Safe

How to queue lambda function into Qt's event loop?

Your problem is of How to leverage Qt to make a QObject method thread-safe? Let's adapt the solutions offered there to your use case. First, let's factor out the safety check:

bool isSafe(QObject * obj) {
Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
auto thread = obj->thread() ? obj->thread() : qApp->thread();
return thread == QThread::currentThread();
}

The approach you suggested takes a functor, and lets the compiler deal with packing up the arguments (if any) within the functor:

template <typename Fun> void postCall(QObject * obj, Fun && fun) {
qDebug() << __FUNCTION__;
struct Event : public QEvent {
using F = typename std::decay<Fun>::type;
F fun;
Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
~Event() { fun(); }
};
QCoreApplication::postEvent(
obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}

A second approach stores the copies of all the parameters explicitly within the event and doesn't use a functor:

template <typename Class, typename... Args>
struct CallEvent : public QEvent {
// See https://stackoverflow.com/a/7858971/1329652
// See also https://stackoverflow.com/a/15338881/1329652
template <int ...> struct seq {};
template <int N, int... S> struct gens { using type = typename gens<N-1, N-1, S...>::type; };
template <int ...S> struct gens<0, S...> { using type = seq<S...>; };
template <int ...S> void callFunc(seq<S...>) { (obj->*method)(std::get<S>(args)...); }
Class * obj;
void (Class::*method)(Args...);
std::tuple<typename std::decay<Args>::type...> args;
CallEvent(Class * obj, void (Class::*method)(Args...), Args&&... args) :
QEvent(QEvent::None), obj(obj), method(method), args(std::move<Args>(args)...) {}
~CallEvent() { callFunc(typename gens<sizeof...(Args)>::type()); }
};

template <typename Class, typename... Args> void postCall(Class * obj, void (Class::*method)(Args...), Args&& ...args) {
qDebug() << __FUNCTION__;
QCoreApplication::postEvent(
obj->thread() ? static_cast<QObject*>(obj) : qApp, new CallEvent<Class, Args...>{obj, method, std::forward<Args>(args)...});
}

It's used as follows:

struct Class : QObject {
int num{};
QString str;
void method1(int val) {
if (!isSafe(this))
return postCall(this, [=]{ method1(val); });
qDebug() << __FUNCTION__;
num = val;
}
void method2(const QString &val) {
if (!isSafe(this))
return postCall(this, &Class::method2, val);
qDebug() << __FUNCTION__;
str = val;
}
};

A test harness:

// https://github.com/KubaO/stackoverflown/tree/master/questions/safe-method-40382820
#include <QtCore>

// above code

class Thread : public QThread {
public:
Thread(QObject * parent = nullptr) : QThread(parent) {}
~Thread() { quit(); wait(); }
};

void moveToOwnThread(QObject * obj) {
Q_ASSERT(obj->thread() == QThread::currentThread());
auto thread = new Thread{obj};
thread->start();
obj->moveToThread(thread);
}

int main(int argc, char ** argv) {
QCoreApplication app{argc, argv};
Class c;
moveToOwnThread(&c);

const auto num = 44;
const auto str = QString::fromLatin1("Foo");
c.method1(num);
c.method2(str);
postCall(&c, [&]{ c.thread()->quit(); });
c.thread()->wait();
Q_ASSERT(c.num == num && c.str == str);
}

Output:

postCall 
postCall
postCall
method1
method2

The above compiles and works with either Qt 4 or Qt 5.

See also this question, exploring various ways of invoking functors in other thread contexts in Qt.

How can I have QThread emit a heap-allocated QObject without leaking?

Use smart pointer (e.g., QSharedPointer) instead a normal pointer:

DataClass *result = new DataClass;

should be replaced with

QSharedPointer<DataClass> result = QSharedPointer<DataClass>(new DataClass);

Then, you could safely pass it somewhere and do not worry about deleting it. When it is out of the last scope where it can be used, the object will be automatically destroyed.

Qt thread ID is equal to MainWindows? (moveToThread)

There are several issues that I'll address in random order.

First of all, using thread IDs is bad user experience. Give the threads a descriptive name:

int main(...) {
QApplication app(...);
QThread myThread;
MyObject myObject;
myObject->moveToThread(&myThread);
QThread::currentThread()->setObjectName("mainThread");
myThread.setObjectName("myThread");
...
}

Then use QThread::currentThread()->objectName() to retrieve it. You can also pass QObject* to qDebug() to display the name of the thread:

qDebug() << QThread::currentThread();

Your signal invocation would then become:

QString currentThreadName() {
return QThread::currentThread()->objectName().isEmpty() ?
QStringLiteral("0x%1").arg(QThread::currentThread(), 0, 16) :
QThread::currentThread()->objectName();
}

...
emit requestFileContent(
QStringLiteral("Emitting from thread \"%1\"\n").arg(currentThreadName));

Then, use the above to deal with the thread you've created:

    auto thread = new QThread(this);
thread->setObjectName("fileThread");
ui.textBrowser->append(QStringLiteral("Worker thread: \"%1\").arg(thread->objectName()));
auto AVC_file = new AVC_File;

AVC_file->moveToThread(thread);
...

But AVC_FileCheck is invoked from the main thread. Whether that's OK or not depends on how that method is implemented. It needs to be thread-safe, see this question for a discussion of that. TL;DR: The following pattern could be a starting point:

class AVC_file : public QObject {
Q_OBJECT
Q_SLOT void fileCheck_impl(QIODevice * dev) {
dev->setParent(this);
...
}
Q_SIGNAL void fileCheck_signal(QIODevice *);
public:
void fileCheck(QIODevice *dev) { fileCheck_signal(dev); }
AVC_file(QObject *parent = nullptr) : QObject(parent) {
connect(this, &AVC_file::fileCheck_signal, this, &AVC_file::fileCheck_impl);
...
}
};

Finally, your existing AVC_fileCheck API is broken. You pass QFile by reference: this won't ever work since it ceases to exist as soon as on_OpenAVCFile_clicked returns. When AVC_file uses that file in its thread, it's a dangling object reference.

Instead, you must pass the ownership of the file to AVC_file, and pass a pointer to an instance that AVC_file will dispose when done with. Or simply let AVC_file open the file for you!

Why is a slot being called in receiver's thread even after using Qt::DirectConnection? How do I ensure that it is called in the other thread?

You have to consider 3 threads in your problem statement:

  1. The thread the receiver lives in
  2. The thread the sender lives in
  3. The thread that is emitting the signal

Queued/BlockingQueued connections ensure that the slot will be executed in the receivers thread.

DirectConnection executes the slot in the current thread. This is not always the thread the sender lives in! In fact, there is no standard way to enforce running a slot in the sender's thread (because usually, the receiver's thread is what you want).

NB: AutoConnection uses QueuedConnection if the current thread and receiver thread are not the same, otherwise DirectConnection

To solve your problem, you could forcably switch to the sender's thread if you're on another thread, like this:

In EmitCaller, add

private: Q_INVOKABLE my_thread_do_emit() { do_emit(); }

Then in the implemetation:

void EmitCaller::do_emit()
{
if (this->thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "my_thread_do_emit", Qt::BlockingQueuedConnection);
} else {
emit my_signal();
}
}

However, I would suggest that you reconsider your design. It seems unusual to call a slot in a certain foreign thread. Maybe there is a problem in your thread affinity setup... (e.g. the receiver should live in the newly created thread)

Qt 5: emit signal from non-Qt thread

The error message tells you exactly what you need to know: you can't use the event loop system without QCoreApplication existing. That's all. All of your exploration into the innards of Qt was educational, but a red herring. None if it matters at all.

only the receiver thread has to have the Qt event loop that will dispatch the event from the queue

That's correct.

Does it mean that if I create QCoreApplication inside QThread, this system should work?

You might create it on any thread (in contrast to QGuiApplication that can only live on the main thread). But make sure that you link statically with Qt. Otherwise, if you're linking with system Qt, you'll become binary incompatible with any process using the same Qt if you create a second instance of the application. Thus, if you use system Qt you can work around by inspecting whether an application instance exists, and only create one if it doesn't exist yet.

Furthermore, you shouldn't really need to create the application instance in a custom thread. Your library should accept an initialization call that should be performed in the main thread of the calling process. This initialization can create an application object if one doesn't exist.

// https://github.com/KubaO/stackoverflown/tree/master/questions/twothreads-41044526
#include <QtCore>

// see http://stackoverflow.com/questions/40382820
template <typename Fun> void safe(QObject * obj, Fun && fun) {
Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
if (Q_LIKELY(obj->thread() == QThread::currentThread()))
return fun();
struct Event : public QEvent {
using F = typename std::decay<Fun>::type;
F fun;
Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
~Event() { fun(); }
};
QCoreApplication::postEvent(
obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}

class Worker : public QObject {
Q_OBJECT
QBasicTimer m_timer;
int n = 0;
void timerEvent(QTimerEvent *event) override {
if (event->timerId() == m_timer.timerId())
emit hasData(n++);
}
public:
Q_SIGNAL void hasData(int);
Q_SLOT void onData(int d) { qDebug() << QThread::currentThread() << "got data" << d; }
void start() {
safe(this, [this]{ m_timer.start(50,this); });
}
void quit() {
safe(this, [this]{ m_timer.stop(); thread()->quit(); });
}
};

class Library {
QByteArray dummy{"dummy"};
int argc = 1;
char *argv[2] = {dummy.data(), nullptr};
QScopedPointer<QCoreApplication> app;
static Library *m_self;
struct {
Worker worker;
QThread thread;
} m_jobs[3];
public:
Library() {
Q_ASSERT(!instance());
m_self = this;
if (!qApp) app.reset(new QCoreApplication(argc, argv));

for (auto & job : m_jobs) {
job.worker.moveToThread(&job.thread);
job.thread.start();
job.worker.start();
QObject::connect(&job.worker, &Worker::hasData, &m_jobs[0].worker, &Worker::onData);
}
}
~Library() {
for (auto &job : m_jobs) {
job.worker.quit();
job.thread.wait();
}
}
static Library *instance() { return m_self; }
};
Library *Library::m_self;

// API
void initLib() {
new Library;
}

void finishLib() {
delete Library::instance();
}

int main()
{
initLib();
QThread::sleep(3);
finishLib();
}

#include "main.moc"

Background thread with QThread in PyQt

I created a little example that shows 3 different and simple ways of dealing with threads. I hope it will help you find the right approach to your problem.

import sys
import time

from PyQt5.QtCore import (QCoreApplication, QObject, QRunnable, QThread,
QThreadPool, pyqtSignal)

# Subclassing QThread
# http://qt-project.org/doc/latest/qthread.html
class AThread(QThread):

def run(self):
count = 0
while count < 5:
time.sleep(1)
print("A Increasing")
count += 1

# Subclassing QObject and using moveToThread
# http://blog.qt.digia.com/blog/2007/07/05/qthreads-no-longer-abstract
class SomeObject(QObject):

finished = pyqtSignal()

def long_running(self):
count = 0
while count < 5:
time.sleep(1)
print("B Increasing")
count += 1
self.finished.emit()

# Using a QRunnable
# http://qt-project.org/doc/latest/qthreadpool.html
# Note that a QRunnable isn't a subclass of QObject and therefore does
# not provide signals and slots.
class Runnable(QRunnable):

def run(self):
count = 0
app = QCoreApplication.instance()
while count < 5:
print("C Increasing")
time.sleep(1)
count += 1
app.quit()

def using_q_thread():
app = QCoreApplication([])
thread = AThread()
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())

def using_move_to_thread():
app = QCoreApplication([])
objThread = QThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
objThread.started.connect(obj.long_running)
objThread.finished.connect(app.exit)
objThread.start()
sys.exit(app.exec_())

def using_q_runnable():
app = QCoreApplication([])
runnable = Runnable()
QThreadPool.globalInstance().start(runnable)
sys.exit(app.exec_())

if __name__ == "__main__":
#using_q_thread()
#using_move_to_thread()
using_q_runnable()


Related Topics



Leave a reply



Submit