Can anybody explain the Qt::BlockingQueuedConnection with example ?
-
@Qt-embedded-developer said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
Because i am not understanding when to use Qt::BlockingQueuedConnection .
Then you should not use it.
BlockingQueuedConnection should not be used since it does block the current thread until the slots were executed which is mostly a bad design.
@Christian-Ehrlicher then why it get introduced ?
if any thing is bad then we must not introduced in programming language.
if it introduced for any good reason then let me know with real time example (situation)
-
I want to understand the use of Qt::BlockingQueuedConnection with example.
Because i am not understanding when to use Qt::BlockingQueuedConnection .
i need real time application of it also
Hi,
The only use case is if you need to wait on a slot in a different thread to be done.
This also means that you are blocking the calling thread hence, don't use it unless it's really the only and last possible solution.
-
@Christian-Ehrlicher then why it get introduced ?
if any thing is bad then we must not introduced in programming language.
if it introduced for any good reason then let me know with real time example (situation)
@Qt-embedded-developer said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
if any thing is bad then we must not introduced in programming language
It was not introduced in a programming language, it's just a functionality in a library.
And just because I saidit should not be used
doesn't mean that there are corner cases where it is needed - otherwise it would not have been added to the library.
Download the Qt source code, search for BlockingQueuedConnection and you will see the corner cases. -
Hi,
The only use case is if you need to wait on a slot in a different thread to be done.
This also means that you are blocking the calling thread hence, don't use it unless it's really the only and last possible solution.
@SGaist said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
The only use case is if you need to wait on a slot in a different thread to be done.
Can you elaborate this with real time application or example.
-
@SGaist said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
The only use case is if you need to wait on a slot in a different thread to be done.
Can you elaborate this with real time application or example.
@Qt-embedded-developer said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
Can you elaborate this with real time application or example.
Download the Qt source code, search for BlockingQueuedConnection and you will see the corner cases.
Isn't this enough?
-
@Qt-embedded-developer said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
Can you elaborate this with real time application or example.
Download the Qt source code, search for BlockingQueuedConnection and you will see the corner cases.
Isn't this enough?
@Christian-Ehrlicher said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
BlockingQueuedConnection
I found below things from source code :
Qt::BlockingQueuedConnection, the method will be invoked in the same way as for Qt::QueuedConnection, except that the current thread will block until the event is delivered. Using this connection type to communicate between objects in the same thread will lead to deadlocks.
-
@Christian-Ehrlicher said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
BlockingQueuedConnection
I found below things from source code :
Qt::BlockingQueuedConnection, the method will be invoked in the same way as for Qt::QueuedConnection, except that the current thread will block until the event is delivered. Using this connection type to communicate between objects in the same thread will lead to deadlocks.
You found the documentation - good start.
-
There are 4 types of connections:
Direct:
Queued:
Blocked queued:
Auto: same as Direct when sender and receiver are on the same thread and Queued when they're on different threads.
It's hard to come up with a good example for the blocking queued connection use because there's rarely any. It blocks the sender thread, so it could be used as synchronization mechanism. A very contrived example would be when a thread produces a set of data to be worked on, then emits a "start work" signal, a bunch of other threads pick up the work with a slot and the producing thread waits until they're done. As I said that's kinda forced, because you would usually handle this without blocking. As others said, if you can't think of a reason to use this type of connection it means you don't have a reason to use it.
I've been using Qt for over a decade and I don't think I ever found a good reason to use it, only a couple of bad ones that got refactored out later on.
-
-
Just wanted to add one real example of using a BlockingQueuedConnection: Error handling. While ideally you can design around errors such that more work can be performed even when they occur, sometimes you can't. This also may apply for events that aren't strictly errors, but impose similar "blocking" situations.
Let's assume you're using the preferred worker-object multithreading model so that your worker object can perform a task in a new thread without blocking the one it spawns it. I'll further assume the most common case for that here, in which the spawning thread is the main/UI thread and you're trying to perform a long task without blocking the UI and/or other small tasks from processing there.
The QThread docs have a good example of this.
Now, let's group the kind of error's that might occur during this work into reasonable categories:
- A simple error the user can do nothing about
- An error that requires a choice to handle but doesn't impede other work
- An error of a particular nature such that the worker thread's logic must fork entirely based on a decision, or potentially outright abandon its task.
The first two can be handled asynchronously, but the third type is by nature blocking and where Qt::BlockingQueuedConnection is useful.
Expanding on the QThread example, you might end up with something like this:
#include <QMessageBox> #include <QInputDialog> #include <QThread> #include <QFile> class Worker : public QObject { Q_OBJECT public slots: void doWork() { // ... here is the expensive or blocking operation /* Try to copy a file. If it fails, we need to prompt the user for a response, * but we don't need it right away */ if(!QFile::copy("/source/path", "/dest/path")) emit actionableErrorOccurred("File copy failed"); // ... more work /* Try to remove a file. If it fails, the user should know, but there's nothing * to be done */ if(!QFile::remove("/delete/me")) emit errorOccurred("File deletion failed"); // ... more work /* Check if an imperative file exists. Highly unexpected to be missing, so if it * it is, then it might not make sense to continue and we need to know what the * user want's to do before proceeding. */ bool abort = false; emit blockingErrorOccurred(&abort, "Critical file missing, abort?"); if(abort) { emit workFinished("Failed - User aborted"); return; } // ... more work emit workFinished("Success"); } void handleActionResponse(const QString& response) { ... } // Handle earlier choices here signals: void workFinished(const QString& result); void errorOccurred(const QString& msg); void actionableErrorOccurred(const QString& msg); void blockingErrorOccurred(bool* response, const QString& msg); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller(QObject* parent) : QObject(parent) { /* NOTE: Connections made to "worker" here without a connection type specified are * all Qt::QueuedConnection since it lives in another thread. */ // Create worker Worker* worker = new Worker; worker->moveToThread(&workerThread); // Operation connect(worker, &Worker::workFinished, this, &Controller::handleResult); connect(worker, &Worker::workFinished, &workerThread, &QThread::quit); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::operate, worker, &Worker::doWork); // Error handling connect(worker, &Worker::errorOccurred, this, &Controller::handleError); connect(worker, &Worker::actionableErrorOccurred, this, &Controller::handleActionableError); connect(this, &Controller::actionSelected, worker, &Worker::handleActionResponse); connect(worker, &Worker::blockingErrorOccurred, this, &Controller::handleBlockingError, Qt::BlockingQueuedConnection); /* Specifying Qt::BlockingConnection on this last connection makes the worker * object appropriately block when it encounters an error that requires immediate * user input to handle */ // Start thread workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } void startWork() { emit operate(); } private slots: void handleResult(const QString& result) { ... } // Handle result void handleError(const QString& msg) { QMessageBox::warning(nullptr, "Issue", msg); } void handleActionableError(const QString& msg) { static const QStringList options{"Option 1", "Option 2", "Option 3"}; QString action = QInputDialog::getItem(nullptr, "Actionable Issue", msg, options); emit actionSelected(action); } void handleBlockingError(bool* response, const QString& msg) { Q_ASSERT(response); auto button = QMessageBox::critical(nullptr, "Major Error", msg, QMessageBox::Yes | QMessageBox::No); *response = button == QMessageBox::Yes; } signals: void operate(); void actionSelected(const QString& action); }; MainWindow::doLongOperation() { Controller* workController = new Controller(this); workController->startWork(); } #include "main.moc"
If the worker in the worker thread runs into a situation where it's unsure on how to proceed without a choice being made, it can essentially request a response from the UI thread for user input on how to handle the situation and wait for the response, thanks to Qt::BlockingQueuedConnection.
Of course this example is heavily cut down, but it's just to give an idea of the use case. You should always try to design execution flow to avoid blocking whenever possible, but that isn't achievable 100% of the time.
-
Just wanted to add one real example of using a BlockingQueuedConnection: Error handling. While ideally you can design around errors such that more work can be performed even when they occur, sometimes you can't. This also may apply for events that aren't strictly errors, but impose similar "blocking" situations.
Let's assume you're using the preferred worker-object multithreading model so that your worker object can perform a task in a new thread without blocking the one it spawns it. I'll further assume the most common case for that here, in which the spawning thread is the main/UI thread and you're trying to perform a long task without blocking the UI and/or other small tasks from processing there.
The QThread docs have a good example of this.
Now, let's group the kind of error's that might occur during this work into reasonable categories:
- A simple error the user can do nothing about
- An error that requires a choice to handle but doesn't impede other work
- An error of a particular nature such that the worker thread's logic must fork entirely based on a decision, or potentially outright abandon its task.
The first two can be handled asynchronously, but the third type is by nature blocking and where Qt::BlockingQueuedConnection is useful.
Expanding on the QThread example, you might end up with something like this:
#include <QMessageBox> #include <QInputDialog> #include <QThread> #include <QFile> class Worker : public QObject { Q_OBJECT public slots: void doWork() { // ... here is the expensive or blocking operation /* Try to copy a file. If it fails, we need to prompt the user for a response, * but we don't need it right away */ if(!QFile::copy("/source/path", "/dest/path")) emit actionableErrorOccurred("File copy failed"); // ... more work /* Try to remove a file. If it fails, the user should know, but there's nothing * to be done */ if(!QFile::remove("/delete/me")) emit errorOccurred("File deletion failed"); // ... more work /* Check if an imperative file exists. Highly unexpected to be missing, so if it * it is, then it might not make sense to continue and we need to know what the * user want's to do before proceeding. */ bool abort = false; emit blockingErrorOccurred(&abort, "Critical file missing, abort?"); if(abort) { emit workFinished("Failed - User aborted"); return; } // ... more work emit workFinished("Success"); } void handleActionResponse(const QString& response) { ... } // Handle earlier choices here signals: void workFinished(const QString& result); void errorOccurred(const QString& msg); void actionableErrorOccurred(const QString& msg); void blockingErrorOccurred(bool* response, const QString& msg); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller(QObject* parent) : QObject(parent) { /* NOTE: Connections made to "worker" here without a connection type specified are * all Qt::QueuedConnection since it lives in another thread. */ // Create worker Worker* worker = new Worker; worker->moveToThread(&workerThread); // Operation connect(worker, &Worker::workFinished, this, &Controller::handleResult); connect(worker, &Worker::workFinished, &workerThread, &QThread::quit); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::operate, worker, &Worker::doWork); // Error handling connect(worker, &Worker::errorOccurred, this, &Controller::handleError); connect(worker, &Worker::actionableErrorOccurred, this, &Controller::handleActionableError); connect(this, &Controller::actionSelected, worker, &Worker::handleActionResponse); connect(worker, &Worker::blockingErrorOccurred, this, &Controller::handleBlockingError, Qt::BlockingQueuedConnection); /* Specifying Qt::BlockingConnection on this last connection makes the worker * object appropriately block when it encounters an error that requires immediate * user input to handle */ // Start thread workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } void startWork() { emit operate(); } private slots: void handleResult(const QString& result) { ... } // Handle result void handleError(const QString& msg) { QMessageBox::warning(nullptr, "Issue", msg); } void handleActionableError(const QString& msg) { static const QStringList options{"Option 1", "Option 2", "Option 3"}; QString action = QInputDialog::getItem(nullptr, "Actionable Issue", msg, options); emit actionSelected(action); } void handleBlockingError(bool* response, const QString& msg) { Q_ASSERT(response); auto button = QMessageBox::critical(nullptr, "Major Error", msg, QMessageBox::Yes | QMessageBox::No); *response = button == QMessageBox::Yes; } signals: void operate(); void actionSelected(const QString& action); }; MainWindow::doLongOperation() { Controller* workController = new Controller(this); workController->startWork(); } #include "main.moc"
If the worker in the worker thread runs into a situation where it's unsure on how to proceed without a choice being made, it can essentially request a response from the UI thread for user input on how to handle the situation and wait for the response, thanks to Qt::BlockingQueuedConnection.
Of course this example is heavily cut down, but it's just to give an idea of the use case. You should always try to design execution flow to avoid blocking whenever possible, but that isn't achievable 100% of the time.
@oblivioncth said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
The first two can be handled asynchronously, but the third type is by nature blocking and where Qt::BlockingQueuedConnection is useful.
Expanding on the QThread example, you might end up with something like this:Well, it is certainly the most easy way to reason about things. However, it can actually be solved asynchronously. A general advice would be to split up the work into several chunks with individual slots. One of the main points of object oriented programming is that objects can store state. This would mean that you would have a new slot:
void doWorkPart2(bool abort) { if(abort) { emit workFinished("Failed - User aborted"); return; } // ... more work emit workFinished("Success"); }
blockingErrorOccured
should trigger a slot which in itself will trigger a signal thatdoWorkPart2
could be connected to.I do understand that this disrupts the flow of the algorithm. However, you could use a lambda directly inplace instead if you work out how to connect the signal carrying the
abort
to the lambda. This would keep the logic local.PS: To be honest, we migrated some old software over to Qt. In order to not have to rewrite everything (because who has the time and money?) we wrote a little wrapper
guiThread(...)
where we could just place the code for the dialog (not a separate slot to be triggered withblockingErrorOccured
). So, in our case even the GUI code is in situ (using a lambda and QMetaObject::invokeMethod under the hood). -
@oblivioncth said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
The first two can be handled asynchronously, but the third type is by nature blocking and where Qt::BlockingQueuedConnection is useful.
Expanding on the QThread example, you might end up with something like this:Well, it is certainly the most easy way to reason about things. However, it can actually be solved asynchronously. A general advice would be to split up the work into several chunks with individual slots. One of the main points of object oriented programming is that objects can store state. This would mean that you would have a new slot:
void doWorkPart2(bool abort) { if(abort) { emit workFinished("Failed - User aborted"); return; } // ... more work emit workFinished("Success"); }
blockingErrorOccured
should trigger a slot which in itself will trigger a signal thatdoWorkPart2
could be connected to.I do understand that this disrupts the flow of the algorithm. However, you could use a lambda directly inplace instead if you work out how to connect the signal carrying the
abort
to the lambda. This would keep the logic local.PS: To be honest, we migrated some old software over to Qt. In order to not have to rewrite everything (because who has the time and money?) we wrote a little wrapper
guiThread(...)
where we could just place the code for the dialog (not a separate slot to be triggered withblockingErrorOccured
). So, in our case even the GUI code is in situ (using a lambda and QMetaObject::invokeMethod under the hood).@SimonSchroeder said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
Well, it is certainly the most easy way to reason about things. However, it can actually be solved asynchronously. A general advice would be to split up the work into several chunks with individual slots. One of the main points of object oriented programming is that objects can store state. This would mean that you would have a new slot:
In the spirit of never say never, perhaps I shouldn't have stated "there are situations where this must be used" and rather, "there are situations where this is reasonable. I do still think there are specific cases where it is nearly outright required, but yes, those are extremely rare.
Assuming I understand you correctly:
// In controller private slots: handlePartOneResult(Result r) { bool abort = false; if(Result == Result::BlockingError) abort= ... // Ask user if they want to continue via msg or something emit startPartTwo(abort); } handlePartTwoResult(Result r) { ... } // In worker public slots: void doPartOne() { ... return Result::... } void doPartTwo(bool abort) { if(abort) return; ... }
Yes, this is technically better if the focus is to absolutely keep the event loop in the worker thread unblocked no matter what, which to be fair is a good goal is most cases; however, as you mentioned yourself this can eventually come at the cost of readability/complexity since you have to break up your code at every point where a decision fork that requires talking to the UI thread needs to be made, which could get quite messy (yet of course sometimes you just deal with this anyway).
In this topic there are really two types of "blocking" we're talking about here: Execution blocking (generally the default meaning), and "decision blocking", logic blocking, or whatever you want to call it. I more so was referring to the latter when I said "sometimes you're stuck in a blocking situation"; situations like dependent steps in a CI/CD workflow. Remember, this is a worker thread, and in these situations you might not be able to do any more work at all before getting the answer. So, while you can structure the thread like you suggest and avoid blocking the event loop... what are you blocking it from? If your worker thread also receives events from other places and takes care of other, more independent tasks then by all means you need to keep that loop spinning, as would be the case in more complex designs; however, if this is just a simple worker thread dedicated to handling a mostly interdependent pipeline of tightly related tasks and at some point it becomes "logically blocked", I don't think it's unreasonable to simply block execution while waiting for a response. In this scenario the thread can't do anything else anyway.
At least for me, most of the time I add a second thread (ignoring the additional ones you're already going to have due to system libraries and the like), I'm probably adding a third and maybe more. Usually I'll have one thread (whether the default main/UI thread or a new primary thread) that never ever blocks and handles event processing while keeping work tight so that loop is as free as possible. This thread will then dispatch work as needed to one or more dedicated worker threads that tend to be much more linear as I described earlier. So in the rare event those need sudden unexpected intervention, blocking their execution is generally a non-issue and keeps things cleaner. Additionally, the dispatcher thread itself can intercept the requests instead of forwarding them and override the return-via-parameter value quickly should the response have actually been determined earlier, or determined to be no longer needed. Perhaps this has just worked out for me since a large number of my projects have involved at least some sections that are very linear, yet long enough to constitute being in a separate thread. There are many types of applications where this approach would be a non-starter pretty much always.
To be clear though, I suppose it is important to encourage people newer to the event loop and threads to almost always prefer non-blocking solutions, especially in the main thread.
@SimonSchroeder said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
However, you could use a lambda directly
This would be much more like using Javascipt's Promises (which Qt does have an equivalent too that I've never messed with), which I do think is a better happy medium than potential slot madness if there would be a high number, though you can potentially end up with a lot nested lambdas which isn't great either.
By no means do I claim to be an expert in handling multi-threaded applications, I'm sure there are many things I could have better parallelized but didn't, I just wanted to say that I think in some situations Qt::BlockingQueuedConnections has a good use :)
-
@SimonSchroeder said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
Well, it is certainly the most easy way to reason about things. However, it can actually be solved asynchronously. A general advice would be to split up the work into several chunks with individual slots. One of the main points of object oriented programming is that objects can store state. This would mean that you would have a new slot:
In the spirit of never say never, perhaps I shouldn't have stated "there are situations where this must be used" and rather, "there are situations where this is reasonable. I do still think there are specific cases where it is nearly outright required, but yes, those are extremely rare.
Assuming I understand you correctly:
// In controller private slots: handlePartOneResult(Result r) { bool abort = false; if(Result == Result::BlockingError) abort= ... // Ask user if they want to continue via msg or something emit startPartTwo(abort); } handlePartTwoResult(Result r) { ... } // In worker public slots: void doPartOne() { ... return Result::... } void doPartTwo(bool abort) { if(abort) return; ... }
Yes, this is technically better if the focus is to absolutely keep the event loop in the worker thread unblocked no matter what, which to be fair is a good goal is most cases; however, as you mentioned yourself this can eventually come at the cost of readability/complexity since you have to break up your code at every point where a decision fork that requires talking to the UI thread needs to be made, which could get quite messy (yet of course sometimes you just deal with this anyway).
In this topic there are really two types of "blocking" we're talking about here: Execution blocking (generally the default meaning), and "decision blocking", logic blocking, or whatever you want to call it. I more so was referring to the latter when I said "sometimes you're stuck in a blocking situation"; situations like dependent steps in a CI/CD workflow. Remember, this is a worker thread, and in these situations you might not be able to do any more work at all before getting the answer. So, while you can structure the thread like you suggest and avoid blocking the event loop... what are you blocking it from? If your worker thread also receives events from other places and takes care of other, more independent tasks then by all means you need to keep that loop spinning, as would be the case in more complex designs; however, if this is just a simple worker thread dedicated to handling a mostly interdependent pipeline of tightly related tasks and at some point it becomes "logically blocked", I don't think it's unreasonable to simply block execution while waiting for a response. In this scenario the thread can't do anything else anyway.
At least for me, most of the time I add a second thread (ignoring the additional ones you're already going to have due to system libraries and the like), I'm probably adding a third and maybe more. Usually I'll have one thread (whether the default main/UI thread or a new primary thread) that never ever blocks and handles event processing while keeping work tight so that loop is as free as possible. This thread will then dispatch work as needed to one or more dedicated worker threads that tend to be much more linear as I described earlier. So in the rare event those need sudden unexpected intervention, blocking their execution is generally a non-issue and keeps things cleaner. Additionally, the dispatcher thread itself can intercept the requests instead of forwarding them and override the return-via-parameter value quickly should the response have actually been determined earlier, or determined to be no longer needed. Perhaps this has just worked out for me since a large number of my projects have involved at least some sections that are very linear, yet long enough to constitute being in a separate thread. There are many types of applications where this approach would be a non-starter pretty much always.
To be clear though, I suppose it is important to encourage people newer to the event loop and threads to almost always prefer non-blocking solutions, especially in the main thread.
@SimonSchroeder said in Can anybody explain the Qt::BlockingQueuedConnection with example ?:
However, you could use a lambda directly
This would be much more like using Javascipt's Promises (which Qt does have an equivalent too that I've never messed with), which I do think is a better happy medium than potential slot madness if there would be a high number, though you can potentially end up with a lot nested lambdas which isn't great either.
By no means do I claim to be an expert in handling multi-threaded applications, I'm sure there are many things I could have better parallelized but didn't, I just wanted to say that I think in some situations Qt::BlockingQueuedConnections has a good use :)
@oblivioncth I completely agree with what you are saying. It would be nice for the future if we could use coroutines in these contexts. Just a simple co_yield to pause execution in the worker thread while waiting for the GUI and then resuming just at that same point in the code. One can dream...
-
@oblivioncth I completely agree with what you are saying. It would be nice for the future if we could use coroutines in these contexts. Just a simple co_yield to pause execution in the worker thread while waiting for the GUI and then resuming just at that same point in the code. One can dream...
Hmmm... on that note I imagine I could come up with something where the initial doWork() slot manages a queue of of coroutine handles/requests so that when I normally emit with a blocking queued connection in some long function I could instead co_yield a pointer to a value I want set, with the doWork() function then storing the coroutine resume handle in a FIFO queue and emitting a non-blocking signal to to the controller in the other thread which can then respond back with the desired value in an also non-blocking manner. The responding slot then pulls the corresponding request out of the queue, dereferences the pointer and sets it to the passed value, and then uses the handle to resume the yielded function.
This would still allow logical blocking when it makes sense while keeping the event loop open for when I would need that thread handling other events. I do like the sounds of this. It would be a good reason to experiment with C++20 coroutines, which (along with ranges to a degree) are one of its features I haven't really used.
It doesn't solve the most annoying case which is having to regularly check (by yielding or checking a flag) in the long function if an abort request has come from the outside, but that's just how it is. That's where splitting up work into really tiny chunks that ideally can go in a queue where the event loop is returned to after each is perfect if you can do it.
But yea, I hope that coroutines in general are expanded upon and eventually Qt implements some slick integrations with them. Although it may be a pipe dream, being able to co_yield directly to the event loop, while maybe even emitting a signal, would be awesome.
-
A side question:
When sender and receiver are in the same thread, whyQt::BlockingQueuedConnection
can't behave asQt::DirectConnection
instead of deadlocking?@mdrost Because it has
Queued
in the name, so it is put in a queue. -
A side question:
When sender and receiver are in the same thread, whyQt::BlockingQueuedConnection
can't behave asQt::DirectConnection
instead of deadlocking?@mdrost And a
DirectConnection
is by definition always blocking.