Russian Qt Forum
Ноябрь 22, 2024, 00:41 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: [Решено] Thread tried to wait on itself  (Прочитано 11289 раз)
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 872


Мы должны приносить пользу людям.


Просмотр профиля
« : Март 15, 2021, 09:01 »

Коллеги,
в одной библиотеке встретил любопытный способ использования QThread, попробовал на примере, приведенном в справке Qt (https://doc.qt.io/qt-5/qthread.html). В примере перенос объекта Worker в поток осуществляется в конструкторе Controller с ожиданием его завершения в деструкторе:

Код
C++ (Qt)
Controller::Controller(QObject *parent) : QObject(parent)
{
   worker = new Worker;
   worker->moveToThread(&workerThread);
   workerThread.start();
}
Controller::~Controller() {
   workerThread.quit();
   workerThread.wait();
   worker->deleteLater();
}
 
А в библиотеке, о которой я упоминал, это делается в самом Worker:

Код
C++ (Qt)
Worker::Worker(QObject *parent) : QObject(parent)
{
   workerThread = new QThread;
   moveToThread(workerThread);
   workerThread->start();
}
 
Worker::~Worker() {
   workerThread->quit();
   workerThread->wait();
   workerThread->deleteLater();
}

Но при этом формируется предупреждение "QThread::wait: Thread tried to wait on itself". Это сообщение понятно: ждать в себе завершения самого себя бессмысленно)) Но в чем принципиальная ошибочность моего кода?
В присоединенном архиве - пример второго способа.
« Последнее редактирование: Март 15, 2021, 20:11 от sergek » Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
ssoft
Программист
*****
Offline Offline

Сообщений: 584


Просмотр профиля
« Ответ #1 : Март 15, 2021, 11:42 »

ждать в себе завершения самого себя бессмысленно)).

Это ж и есть ответ на вопрос.

Вызов деструктора Worker производится в активности потока workerThread, который и предлагается подождать с помощью workerThread->wait().
В первом же варианте вызов деструктора Controller производится в активности другого (скорее всего, главного) потока, связанного с объектом Controller. Здесь можно и подождать workerThread->wait().
Записан
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 872


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #2 : Март 15, 2021, 12:22 »

Собственно, это всего лишь предупреждение, и wait в этом случае ничего не делает. Убираем wait, остается выход из циклов обработки событий quit и отложенное удаление объекта QThread с помощью deleteLater. Вопрос остается - это правильно?
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Март 15, 2021, 12:44 »

Собственно, это всего лишь предупреждение, и wait в этом случае ничего не делает. Убираем wait, остается выход из циклов обработки событий quit и отложенное удаление объекта QThread с помощью deleteLater. Вопрос остается - это правильно?
Может избавиться от сообщения так
Код
C++ (Qt)
Worker::~Worker() {
   workerThread->quit();
   if (QThread::currentThread() != workerThread)
    workerThread->wait();
   workerThread->deleteLater();
}
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 584


Просмотр профиля
« Ответ #4 : Март 15, 2021, 12:49 »

Собственно, это всего лишь предупреждение, и wait в этом случае ничего не делает. Убираем wait, остается выход из циклов обработки событий quit и отложенное удаление объекта QThread с помощью deleteLater. Вопрос остается - это правильно?

Удаление объекта QThread само по себе не завершает активность (Note that deleting a QThread object will not stop the execution of the thread it manages.).
Метод wait же ожидает завершения активности.

В общем случае, второй вариант допускает "подвешивание" незавершенных активностей, особенно если они реализованы в виде бесконечных циклов (когда реализована собственная версия метода MyThread::run() ).
« Последнее редактирование: Март 15, 2021, 12:59 от ssoft » Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 584


Просмотр профиля
« Ответ #5 : Март 15, 2021, 12:54 »

Всё даже может быть намного хуже, если полностью прочитать документацию)).
Цитировать
Note that deleting a QThread object will not stop the execution of the thread it manages. Deleting a running QThread (i.e. isFinished() returns false) will result in a program crash. Wait for the finished() signal before deleting the QThread.
Записан
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 872


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #6 : Март 15, 2021, 12:57 »

Может избавиться от сообщения так
Именно так и реализовано в Qt ))
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 872


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #7 : Март 15, 2021, 13:01 »

Всё даже может быть намного хуже, если полностью прочитать документацию)).
Цитировать
Note that deleting a QThread object will not stop the execution of the thread it manages. Deleting a running QThread (i.e. isFinished() returns false) will result in a program crash. Wait for the finished() signal before deleting the QThread.
Если не ошибаюсь, это предупреждение об прямом удалении с помощью delete. Я ж использую deleteLater. Правда, глубоко не залезал в реализацию, а там стоит wait))
Так можно или нельзя и почему?
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
ssoft
Программист
*****
Offline Offline

Сообщений: 584


Просмотр профиля
« Ответ #8 : Март 15, 2021, 13:11 »

Вот пример по шагам

Код
C++ (Qt)
// деструктор вызывается в потоке workerThread
Worker::~Worker() {
   // в потоке workerThread обращаемся к объекту главного потока workerThread
   // (возможны проблемы с конкурентным доступом) с командой завершения QEventLoop
   workerThread->quit();
 
   // в потоке workerThread обращаемся к объекту главного потока workerThread
   // (возможны проблемы с конкурентным доступом) с командой ожидания завершения активности данного потока
   // получаем игнорирование команды и не завершенный поток workerThread
   workerThread->wait();
 
   // в очередь главный поток отправляем событие удаления объекта workerThread,
   // которое может быть обработано в главном потоке даже до завершения этого деструктора,
   // когда активность потока workerThread фактически не завершена.
   workerThread->deleteLater();
}
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #9 : Март 15, 2021, 13:31 »

Но в чем принципиальная ошибочность моего кода?

Так у вас телега впереди лошади.
QThread - это объект-хэндл треда, а не сам тред (сам тред - это то, что в run()). Хэндл нужен для управления тредом из родительского (часто главного) треда - родительский поток запускает новый поток, он же его останавливает и дожидается.
А так у вас получается что владелец потока (объект Worker) перемещен внутрь этого потока, а тред не может заджойнить сам себя.
Записан
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 872


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #10 : Март 15, 2021, 14:00 »

Хэндл нужен для управления тредом из родительского (часто главного) треда - родительский поток запускает новый поток, он же его останавливает и дожидается.
Спасибо за напоминание, что "хэндл" живет в потоке, в котором он создан)) Но откуда следует, что остановить поток (quit) можно только из того потока, откуда он был запущен?
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #11 : Март 15, 2021, 14:34 »

Спасибо за напоминание, что "хэндл" живет в потоке, в котором он создан)) Но откуда следует, что остановить поток (quit) можно только из того потока, откуда он был запущен?

Ни откуда, quit() можно звать из любого потока.
Нельзя звать wait() из потока потому что, по сути, wait() - это join() - вы ждете когда две нитки сольются в одну (ту, которая вызвала wait()/join()). Как нитка может поджойнится сама с собой?
« Последнее редактирование: Март 15, 2021, 14:50 от Авварон » Записан
sergek
Гипер активный житель
*****
Offline Offline

Сообщений: 872


Мы должны приносить пользу людям.


Просмотр профиля
« Ответ #12 : Март 15, 2021, 14:53 »

Иначе говоря, код
Код
C++ (Qt)
Worker::~Worker() {
   workerThread->quit();
   workerThread->deleteLater();
}
 
корректен?
Записан

Qt 5.13.0 Qt Creator 5.0.1
Win10, Ubuntu 20.04
ssoft
Программист
*****
Offline Offline

Сообщений: 584


Просмотр профиля
« Ответ #13 : Март 15, 2021, 15:12 »

Спасибо за напоминание, что "хэндл" живет в потоке, в котором он создан)) Но откуда следует, что остановить поток (quit) можно только из того потока, откуда он был запущен?

Это QObject, с которым могут связаны какие-нибудь сигналы, и слоты вызываются в основном потоке. Сама обертка QThread не помечена, как thread-safe, таким образом одновременный вызов методов из разных потоков может привести к плачевным результатам.

Иначе говоря, код
Код
C++ (Qt)
Worker::~Worker() {
   workerThread->quit();
   workerThread->deleteLater();
}
 
корректен?

Я считаю, что некорректен, и не стал бы его использовать, так как содержит потенциальную проблему - сегодня работает, а завтра ...
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #14 : Март 15, 2021, 15:35 »

Иначе говоря, код
Код
C++ (Qt)
Worker::~Worker() {
   workerThread->quit();
   workerThread->deleteLater();
}
 
корректен?
Это не так уж важно Улыбающийся, плохо уже то что создается почва для (ненужных) раздумий. Вызов quit останавливает loop нитки, но нитка еще не завершилась.
Цитировать
Since Qt 4.8, if deleteLater() is called on an object that lives in a thread with no running event loop, the object will be destroyed when the thread finishes.
Т.е. если вызов был из workerThread - сработает. А вот из др - хз. Напр вызывающий вытеснен между quit и deleteLater, а когда проснулся - нитка уже завершена  

Edit: а, так из другой дкструктор вызваться и не сможет 
« Последнее редактирование: Март 15, 2021, 15:47 от Igors » Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.285 секунд. Запросов: 22.