В своем проекте пытаюсь реализовать подобие механизма async/await операций на Qt c использование QEventLoop. Приведу простейший пример окна с одной кнопкой, по нажатию которой должен вызывать код, не блокирующий основной (GUI) поток.
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QPushButton *button = new QPushButton("button",this);
connect(button,&QPushButton::clicked,this,&MainWindow::onClicked);
}
void MainWindow::onClicked()
{
qDebug() << time() << " onClicked start ";
QEventLoop loop;
QtConcurrent::run([&loop]() {
qDebug() << time() << " Thread start: " << (void*)QThread::currentThread();
//doSomething
QThread::currentThread()->sleep(10);
QMetaObject::invokeMethod(&loop,"quit",Qt::QueuedConnection)
qDebug() << time() << " Thread finish: " << (void*)QThread::currentThread();
});
loop.exec();
qDebug() << time() << " onClicked finish ";
}
При однократном нажатии все замечательно, QtConcurrent запустит задачу, которая выполнится в отдельном потоке, а loop.exec() остановит выполнение функции, но не заблокирует EventLoop всего приложения, окно будет активно, кнопку можно будет нажать еще.
В отладчике будет выведено, примерно следующее:
16:57:08.774 onClicked start
16:57:08.775 Thread start: 0xa83230
16:57:18.775 Thread finish: 0xa83230
16:57:18.775 onClicked finish
Если нажать кнопку несколько раз, то слот вызовется несколько раз и при каждом вызове создадутся объекты QEventLoop, которые остановят выполнение слота в ожидании окончания асинхронных задач, что очевидно.
НО
Выход из цикла обработки событий у этих объектов произойдет одновременно по окончанию последней асинхронной задачи, т.е. после вызова последнего слота quit()
В отладчике следующее:
17:26:28.464 onClicked start
17:26:28.464 Thread start: 0xa83230
17:26:30.988 onClicked start
17:26:30.988 Thread start: 0x8dae20
17:26:37.549 onClicked start
17:26:37.549 Thread start: 0xa349c0
17:26:38.464 Thread finish: 0xa83230
17:26:40.989 Thread finish: 0x8dae20
17:26:47.549 Thread finish: 0xa349c0
17:26:47.549 onClicked finish
17:26:47.549 onClicked finish
17:26:47.549 onClicked finish
Вопрос: почему при создании нескольких объектов QEventLoop, они заканчивают свои циклы обработки только после вызова слота quit() каждого из объектов и как это изменить?
В Qt используется приватный мьютекс у объекта потока (QThread) с атомарным счётчиком ссылок (QAtomicInt):
// qeventloop.cpp, стр. 164
QMutexLocker locker(&static_cast<QThreadPrivate *>
(QObjectPrivate::get(d->threadData->thread))->mutex);
Соответственно, если поток, в котором существуют экземпляры QEventLoop, один и тот же, то покуда счётчик ссылок quitLockRef (см. файл qeventloop_p.h) не уйдёт в ноль, каждый инстанс QEventLoop будет бесконечно крутиться в собственном цикле.
Для организации асинхронной реакции на завершение работы QtConcurrent лучше всего подходит QFutureWatcher:
void MainWindow::onClicked() {
qDebug() << time() << " onClicked start ";
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
connect(watcher, &QFutureWatcher<void>::finished
, this, [this,watcher]() {
qDebug() << time() << " onClicked finish ";
watcher->deleteLater();
});
QFuture<void> future
= QtConcurrent::run([this]() {
qDebug() << time() << " Thread start: "
<< (void*)QThread::currentThread();
//doSomething
QThread::currentThread()->sleep(10);
qDebug() << time() << " Thread finish: "
<< (void*)QThread::currentThread();
});
watcher->setFuture(future);
}
Сборка персонального компьютера от Artline: умный выбор для современных пользователей