[Solved] How can I evade the error "Cannot send events to objects owned by a different thread"?
-
Hallo QT community,
In my application, there is a QProgressBar that ought to update when a thread progresses. Unfortunately, it does not work. When I connect the signal with Qt::AutoConnection (default), there are no errors, but the progress bar updates after the thread terminates. When I use Qt::DirectConnection, the following error appears immediately after the first emit of the signal:
ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. ..."
The funny thing is that I can use qDebug() and see the proper output on the console while the thread is running, but anything other than that (e.g. updating a QLabel) does not work or only works after thread termination.
The thread itself is embedded into third-party code that should stay as-is. It offers a callback function that offers the current progress (an integer). The callback can be used to e.g. call a method of a QObject that emits a signal or writes information to the console via qDebug(). Now I would like to use that information not only on the console, but to update the progress bar, so the signal should be dealt with at once. Is there anything I might have missed?
I have tried using DirectConnection (brings the above error), AutoConnection (delayed output) and invokeMethod on the slot updating the progress bar (also brings above error). I will gladly test any other ideas. Please help!
-
Hi FranzB,
it is clear that DirectConnect will crash as it is forbidden to do UI stuff / use UI components in a non main thread. I'm not sure why your UI does not update, to see that, I would need to look at your code. Can you provide a small simple example?
-
Hi, sorry for not coming back earlier. I thought I would have some spare time to write a small example on the weekend, but I was too busy.
It is quite hard to provide a simple example as the thread is embedded into third party code. So I can only offer some bits of code that I hope will clarify the problem.
In the QObject setting the GUI with the progress bar and the button to start the threaded operation:
@
void SomeQObject::slotEvoker(QString filename)
{
ThreadTest::callThread(filename);
}void SomeQObject::updateProgress(int percent)
{
this->progressBar->setValue(percent);
//qDebug() << "Progress: " << QString::number(percent);
}
@In the thread-calling file inside a "ThreadTest" namespace:
@
int ThreadTest::showProgress(int percent, void *pContext)
{
someOtherQObject->updateProgress(percent);
//qDebug() << "Progress: " << QString::number(percent);
return 0;
}DWORD WINAPI ThreadTest::runThread(LPVOID pParam)
{
// lengthy operations
}void ThreadTest::callThread(QString filename)
{
ThreadContext tc;
strcpy(tc.fname, filename.toAscii().data());
strcpy(tc.resultfname, filename.toAscii().data());ThirdPartyLib_setProgressCallback(showProgress);
HANDLE threadHandle;
threadHandle = CreateThread(NULL, 0, runThread, (LPVOID) &tc, 0, NULL);
if (threadHandle == NULL)
exit(-1);
WaitForSingleObject(threadHandle, INFINITE);DWORD exitCode;
GetExitCodeThread(threadHandle,&exitCode);
CloseHandle(threadHandle);
if (exitCode==0)
qDebug() << "Thread terminated successfully";
else
qDebug() << "Thread failed";
}
@When the user clicks on a button, "slotEvoker" is called via signal. It defers to "callThread". "callThread" uses a struct "ThreadContext" (irrelevant here, I guess) and sets the callback to "showProgress" via a third-party function. Then it proceeds to create a thread and calls "runThread". This function executes the thread and does some lengthy operations. Whenever some progress is made, "showProgress" is called and there we have the mess.
Short (tldr): slotEvoker->callThread->((runThread->showProgress->updateProgress)) ... (()) is the concurrent part
The qDebug output of "showProgress" works perfectly, it appears instantly when "showProgress" is called. Of course I can also defer output to another object like this: "someOtherQObject->updateProgress(percent)". Now if there is only a qDebug, that works. If "updateProgress" tries to update the GUI (progress bar), it works only after the thread has terminated. There is no output prior to thread termination - which defies the use of a progress bar.
Now I would like to know how I can use the output of the callback function ("showProgress", which works perfectly with qDebug, as I said) to update the progress bar while the thread is running.
Can anyone point me to a solution? I wouldn't have thought that this would appear to me as some kind of rocket science, but I just cannot figure out how to solve that problem.
-
The point uis that the method showProgress is called in the context of the worker thread. Inside worker threads, you are not allowed to call UI stuff like
@
this->progressBar->setValue(percent);
@what you can do is using the meta objects to invoke the method asynchronously:
@
int ThreadTest::showProgress(int percent, void *pContext)
{
// instead of: someOtherQObject->updateProgress(percent);
QMetaObject::invokeMethod(someOtherQObject, // obj
SLOT(updateProgress(int)), // member
Qt::QueuedConnection, // connection type
Q_ARG(int, percent)); // val1//qDebug() << "Progress: " << QString::number(percent); return 0;
}
@If updateProgress is a slot in someOtherQObject, this code should work
Thjis code was not tested, just written in the forum :-)
-
Hi,
Thanks for your answer. Unfortunately, it still does not work. When I use invokeMethod calling the slot as suggested, I receive:QMetaObject::invokeMethod: No such method SomeOtherQObject::1updateProgress(int)(int)
... although that method/slot clearly exists. Writing @SLOT(updateProgress)@ instead of @SLOT(updateProgress(int))@ leads to a similar result:
QMetaObject::invokeMethod: No such method SomeOtherQObject::1updateProgress(int)
Writing "slotProgress" throws no error, but shows the same unwanted behaviour: lazy output only after thread termination.
You are absolutely right about not calling UI stuff from inside worker threads. I just tried everything that came to my mind after the ways proposed in the doc didn't work. So far I have not been able to grasp the inherent problem of the communication between a worker thread and the GUI main event loop - somehow it just doesn't work and I cannot figure out what is wrong. Do you have any further ideas? They would be greatly appreciated.
Regards,
Franz -
So, in general, it works, I know that. I have many multi threadded applications and they communicate via signal/slot or invokeMethode.
If it does not work, it is perhaps a different problem, so If you can reproduce the problem in a small test app, we could have a look at it. But without more source, I have some problems in reproducing it...
-
Ok, back again from a couple of hours rewriting that damn code. First I tried to reproduce the problem in a small test app, but failed to do so, as it worked pretty much right away.
I was a little bewildered, but then decided to wrap all the third-party code into my own QThread, so the QThread would invoke the other thread and just parse its outcome to the GUI. Now it works, although it appears like a somewhat complicated solution. As it is neither a beauty contest nor performance-draining, I will happily take this solution.
Thanks for your help!
-
Came accross the same issue today:
@FranzB Hi, When I use invokeMethod calling the slot as suggested, I receive:
QMetaObject::invokeMethod: No such method SomeOtherQObject::1updateProgress(int)(int)
Solved it like that (look at the way the slot is passed to invokeMethod):
int ThreadTest::showProgress(int percent, void *pContext) { QMetaObject::invokeMethod(someOtherQObject, // obj "updateProgress", // member: don't put parameters Qt::QueuedConnection, // connection type Q_ARG(int, percent)); // val1 return 0; }