Название: Как завершить поток таймера? Отправлено: nvek от Август 24, 2017, 12:48 Опять я, опять, поток, опять таймер. Не понимаю.
Код: #include "Timer.h" как завершить этот поток? QThread: Destroyed while thread is still running Код: m_tmr = new Timer(1000); //класс поток для таймера надо вначале остановить поток, затем удалить таймер. Название: Re: Как завершить поток таймера? Отправлено: nvek от Август 24, 2017, 12:52 а что если это сделать по другому? может быть унаследовать его от обжекта и сделать просто movetothread()/
или попытаться использовать QtConcerent. ведь метод который мене нужен чтобы выполнялся в потоке всего один. Название: Re: Как завершить поток таймера? Отправлено: demaker от Август 28, 2017, 23:17 А какая задача стоит перед Вами?
Название: Re: Как завершить поток таймера? Отправлено: Bepec от Август 29, 2017, 01:59 У вас несколько ошибок.
Во первых вы не понимаете сигнал-слотовой системы и способов её соединения. Во вторых вы пытаетесь отделить (зачем - непонятно) таймер от рабочего потока. В третьих всё общение с потоками должно быть на сигнал-слотах, для облегчения ваших же проблем. PS в некоторые моменты я сам задаюсь вопросом - как же будет работать поток в таком вот случае и иногда не нахожу ответов :) Название: Re: Как завершить поток таймера? Отправлено: demaker от Август 29, 2017, 08:20 Думаю должно работать норм ::)
*.h Код
*.cpp Код
создаем и запускаем поток Код
завершение потока Код
Название: Re: Как завершить поток таймера? Отправлено: ssoft от Август 29, 2017, 13:58 Думаю должно работать норм ::) Код
Здесь кроется серьезная проблема. В такой реализации доступ к методу timeOut() экземпляра типа Thread производится из потока, который с ним никак не связан (в контексте Qt). То есть априорно формируются грабли с одновременным многопоточным вызовом timeOut. Название: Re: Как завершить поток таймера? Отправлено: Bepec от Август 29, 2017, 14:03 to ssoft: Ну, там надо в конструкторе moveToThread(this) дописать и тогда будет работать нормально :D
А так это извечная проблема потоков. Я кстати так до сих пор их и не осилил. К примеру, как правильно остановить событийный QThread ( где в run exec()). Название: Re: Как завершить поток таймера? Отправлено: demaker от Август 29, 2017, 15:12 to ssoft: Ну, там надо в конструкторе moveToThread(this) дописать и тогда будет работать нормально :D ;D да согласен забыл написать ::)А так это извечная проблема потоков. Я кстати так до сих пор их и не осилил. К примеру, как правильно остановить событийный QThread ( где в run exec()). Код: moveToThread(this) Название: Re: Как завершить поток таймера? Отправлено: ssoft от Август 29, 2017, 15:42 to ssoft: Ну, там надо в конструкторе moveToThread(this) дописать и тогда будет работать нормально :D Не будет). Если написать в конструкторе moveToThread(this), то либо проблема переносится в конструктор/деструктор, если экземпляр объекта создается в стеке; либо ваш экземпляр Thread зависнет в куче (memory leaks); либо придется писать какие-нибудь костыли (deleteLater() здесь не поможет). А так это извечная проблема потоков. Я кстати так до сих пор их и не осилил. К примеру, как правильно остановить событийный QThread ( где в run exec()). Да ничего в них проблемного нет). Все зависит от того, как нужно его остановить. Если хотите прервать - одно решение; если хотите завершить все текущие события в очереди - другое; если хотите завершить все события, пока очередь не опустеет (с учетом новых поступлений) - третье. По умолчанию Qt предоставляет метод завершения quit() со своей логикой. Единственная особенность восприятия архитектуры Qt для работы с потоками связана с тем, что экземпляр типа QThread сам по себе организует только инфраструктуру для отдельного потока, но сам к нему не относится ни под каким соусом. То есть экземпляр типа QThread в рамках модели Qt связан с очередью того потока в котором он создан, а не с той, которую он сам организует. А метод moveToThread() - это еще тот костыль (причем встроенный в Qt), который следует использовать с большим количеством оговорок, ограничений и точным пониманием того, что вы делаете на свой страх и риск. Название: Re: Как завершить поток таймера? Отправлено: demaker от Август 29, 2017, 15:59 Ну тогда другой вариант.
Делать бесконечный цикл в run запускать счетчик и считать тики. По дрстижению каког-то значения эмитить сигнал из потока. Но опять же - будет ли это работать правильно и успевать отрабатывать. Название: Re: Как завершить поток таймера? Отправлено: Bepec от Август 29, 2017, 16:00 to ssoft- работа будет происходить в одном потоке. И всё будет хорошо. Абсолютно.
А по поводу moveToThread, ну... такой себе костыль, реализация которого равнозначна реализации потоков :D Название: Re: Как завершить поток таймера? Отправлено: ssoft от Август 30, 2017, 09:23 to ssoft- работа будет происходить в одном потоке. И всё будет хорошо. Абсолютно. Предположим кто-то решил использовать такой класс Пример 1. Размещение в стеке Код
Здесь реализуется непредсказуемое поведение. Как минимум получите предупреждение (QThread: Destroyed while thread is still running), как максимум крэш при обращении к атрибутам экземпляра thread. Пример 2. Размещение в куче Код
В этом случае удалять thread можно только после завершения потока, то есть, как минимум, нужно вызвать два метода quit и wait. Иначе возможны проблемы такие же как и в примере 1. Если не удалять thread, тогда буду утечки памяти (parent указать в этом случае нельзя). Нужно помнить, что к указателю thread теперь нельзя обращаться в этом потоке напрямую (или городить огород с мутексами и т.п.). Нюансов такого использования масса, я еще не все перечислил. Поэтому, если вы точно знаете что делаете, то флаг вам в руки. Но я как-то сомневаюсь, что новички это себе представляют. По самой задаче: Как завершить поток таймера? Если вы используете реализацию отсюда http://www.prog.org.ru/index.php?topic=31547.msg233225#msg233225 , то завершение потока таймера производится обычным способом завершения потока quit(), можно также подождать сам момент завершения с помощью wait(). Название: Re: Как завершить поток таймера? Отправлено: Bepec от Август 30, 2017, 13:34 Пример 1 - самая распространённая ошибка, вы убиваете объект после выхода их функции. Что блин, похоже на ошибку первоклашки в С++. И никоим образом не относится к потоку. Любой класс работающий дольше 5 секунд будет выдавать те же ошибки и поведение :D
Пример 2 - всё правильно. Поток можно удалять ТОЛЬКО после завершения работы. Ответ = вызов в нём deleteLater и quit. Название: Re: Как завершить поток таймера? Отправлено: Igors от Август 30, 2017, 14:10 Есть 2 хорошо известных сущности: timer и thread. Так за каким же <template> называть Timer'ом наследника QThread ??? Чтобы сразу капитально заморочить голову всем, и в первую очередь самому себе?
Чего Вы хотите добиться - я так и не понял. Запустить нитку в которой тикает таймер? QThread имеет методы startTimer и killTimer унаследованные от QObject, почему бы не использовать их? Как всегда. не забыть moveToThread(this) Название: Re: Как завершить поток таймера? Отправлено: ssoft от Август 30, 2017, 16:08 Ответ = вызов в нём deleteLater и quit. Предположим, что deleteLater() для экземпляра thread отработал, это значит еще не был завершен его метод run() и exec() в другом потоке, что может быть чревато последствиями - QThread: Destroyed while thread is still running и все что описано ранее в примере 1. Если же сначала завершить поток, а затем вызвать deleteLater(), то экземпляр thread не будет удален совсем - утечки памяти. Простой пример Код
выводит только Код: Before exec либо, если между deleteLater() и quit() будет достаточно времени (например, пауза или логика какая) Код: Before exec Название: Re: Как завершить поток таймера? Отправлено: Bepec от Август 30, 2017, 17:34 Вы правы, после запуска потока deleteLater просто не работает.
Зато простой delete работает на отлично. Нашёл в исходниках вот такую строчечку Цитировать 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. и вот такую Цитировать From Qt 4.8 onwards, it is possible to deallocate objects that live in a thread that has just ended, by connecting the finished() signal to QObject::deleteLater(). Могу ошибаться, но получается из 1 цитаты, что объект потока удалится лишь после окончания работы потока, в которым был вызван ( основного потока). А из второй безболезненное решение - вешать finished на deleteLater QObject'ов, которые живут в потоке, в том числе и таймеров. PS но вопрос открыт - как же не "ручным" способом удалять поток :) Название: Re: Как завершить поток таймера? Отправлено: Bepec от Август 30, 2017, 17:57 Пока у меня только вариант вешать на finished потока слот с delete sender'a.
Название: Re: Как завершить поток таймера? Отправлено: demaker от Декабрь 12, 2017, 15:02 Ответ = вызов в нём deleteLater и quit. Предположим, что deleteLater() для экземпляра thread отработал, это значит еще не был завершен его метод run() и exec() в другом потоке, что может быть чревато последствиями - QThread: Destroyed while thread is still running и все что описано ранее в примере 1. Если же сначала завершить поток, а затем вызвать deleteLater(), то экземпляр thread не будет удален совсем - утечки памяти. Простой пример Код
выводит только Код: Before exec либо, если между deleteLater() и quit() будет достаточно времени (например, пауза или логика какая) Код: Before exec Поправьте меня - возможно я не прав, но возможно так лучше будет Код
И тогда вот этого не будет Код: QThread: Destroyed while thread is still running Название: Re: Как завершить поток таймера? Отправлено: Kurles от Декабрь 12, 2017, 16:20 По мне дык наследование от QThread и переопределение в нем метода run() не очень хорошая практика. Использую механизм "воркеров", как то всё прозрачней получается, и проще следить за временем жизни объектов. Вот синтетический пример:
Код
Название: Re: Как завершить поток таймера? Отправлено: ssoft от Декабрь 12, 2017, 16:42 Здесь смущает только место использования
Код
Если поток уже завершен, то event loop уже остановлена и некому выполнять deleteLater). А так, использование Worker в разных вариациях предпочтительнее. Наследование от потока тоже корректно, здесь на вкус и цвет, кому как удобнее. Название: Re: Как завершить поток таймера? Отправлено: ssoft от Декабрь 12, 2017, 16:48 Поправьте меня - возможно я не прав, но возможно так лучше будет Код
И тогда вот этого не будет Код: QThread: Destroyed while thread is still running Так тоже будет корректно, но тогда уж лучше сразу переменную на стеке делать и не мучаться с new/delete. Название: Re: Как завершить поток таймера? Отправлено: Kurles от Декабрь 12, 2017, 17:02 Здесь смущает только место использования Поток останавливается ниже в деструкторе. Вызов QThread::quit и последующий вызов QThread::wait() гарантирует как минимум еще одну итерацию event loop потока, которая в свою очередь обеспечит удаление все запланированных к удалению объектов, привязанных к данному потоку.Код
Если поток уже завершен, то event loop уже остановлена и некому выполнять deleteLater). А так, использование Worker в разных вариациях предпочтительнее. Наследование от потока тоже корректно, здесь на вкус и цвет, кому как удобнее. Название: Re: Как завершить поток таймера? Отправлено: Old от Декабрь 12, 2017, 17:17 последующий вызов QThread::wait() гарантирует как минимум еще одну итерацию event loop потока wait ничего не гарантирует, даже более он сам блокирует поток в котором запущен до завершения ожидаемой нитки. В это время eventloop не крутиться.Название: Re: Как завершить поток таймера? Отправлено: Kurles от Декабрь 12, 2017, 17:36 последующий вызов QThread::wait() гарантирует как минимум еще одну итерацию event loop потока wait ничего не гарантирует, даже более он сам блокирует поток в котором запущен до завершения ожидаемой нитки. В это время eventloop не крутиться.Название: Re: Как завершить поток таймера? Отправлено: demaker от Декабрь 12, 2017, 18:01 последующий вызов QThread::wait() гарантирует как минимум еще одну итерацию event loop потока wait ничего не гарантирует, даже более он сам блокирует поток в котором запущен до завершения ожидаемой нитки. В это время eventloop не крутиться.Т.е Вы хотите сказать тем самым, что в книге Боровский А.Н. Qt 4.7+ Практическое программирование на С++ (2012г.) стр 169 Листинг 5.3 (глава 5) НЕ ВЕРЕН И В НЕМ ОШИБКА. Название: Re: Как завершить поток таймера? Отправлено: demaker от Декабрь 12, 2017, 18:03 Поправьте меня - возможно я не прав, но возможно так лучше будет Код
И тогда вот этого не будет Код: QThread: Destroyed while thread is still running Так тоже будет корректно, но тогда уж лучше сразу переменную на стеке делать и не мучаться с new/delete. Вы имейти ввиду переменную на стеке - т.е в run Название: Re: Как завершить поток таймера? Отправлено: kuzulis от Декабрь 12, 2017, 18:22 Wait не нужен если используется концепция с worker-ами, достаточно только quit(). Все там удалится как надо (если были коннекты к deleteLater() у воркета и у треда).
Название: Re: Как завершить поток таймера? Отправлено: Old от Декабрь 12, 2017, 18:24 то есть связка последовательных вызовов QThread::quit + QThread::wait не гарантирует итерации eventLoop'а? Ну откройте уже исходники. :)wait блокирует выполнения нитки в котором он вызван, до завершения ожидаемой нитки. Код
Название: Re: Как завершить поток таймера? Отправлено: Old от Декабрь 12, 2017, 18:26 Т.е Вы хотите сказать тем самым, что в книге Боровский А.Н. Qt 4.7+ Практическое программирование на С++ (2012г.) стр 169 Листинг 5.3 (глава 5) НЕ ВЕРЕН И В НЕМ ОШИБКА. Честно говоря, я понятия не имею, что там написано. :)Легко может быть ошибка. Название: Re: Как завершить поток таймера? Отправлено: ssoft от Декабрь 13, 2017, 08:20 Вы имейти ввиду переменную на стеке - т.е в run Я имею в виду такие костыли). Код
или даже такие Код
А по хорошему используйте QtConcurrent. Wait не нужен если используется концепция с worker-ами, достаточно только quit(). Все там удалится как надо (если были коннекты к deleteLater() у воркета и у треда). По-моему это уже обсуждали http://www.prog.org.ru/index.php?topic=31557.msg233459#msg233459 или что-то другое имеется в виду? Название: Re: Как завершить поток таймера? Отправлено: kuzulis от Декабрь 13, 2017, 09:22 Цитировать По-моему это уже обсуждали http://www.prog.org.ru/index.php?topic=31557.msg233459#msg233459 или что-то другое имеется в виду? Там в примере неправильно использован концепт. В концепте deleteThread() должен вызываться только по сигналам finished() и никак иначе (иначе можно и Машку за ляшку и козу на возу :) ). Название: Re: Как завершить поток таймера? Отправлено: ssoft от Декабрь 13, 2017, 12:02 Ну то бишь верно будет так:
Код
Это будет работать, так как finished() вызывается в потоке, в котором функционирует worker. При этом важно, чтобы connect был выполнен по умолчанию (с опцией Qt::AutoConnection). При этом wait() обязателен до удаления экземпляра thread, иначе снова может получиться) Цитировать QThread: Destroyed while thread is still running This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information. Название: Re: Как завершить поток таймера? Отправлено: Old от Декабрь 13, 2017, 12:36 thread существует в контексте нити в которой он создавался, worker перемещается в контекст нити, которую создаст thread при запуске.
Если был автоматический коннект, то при генерации сигнала будет использоваться очередь рабочей нитки, которая к моменту finished будет уже остановлена и воркер разрушен не будет. :) Название: Re: Как завершить поток таймера? Отправлено: ssoft от Декабрь 13, 2017, 13:33 Происходит примерно следующее (Qt 5.9)
Случай Qt::AutoConnection определяет в runtime каким способом вызывать слот Код
Слот deleteLater() помещает в очередь событие QDeferredDeleteEvent Код
А QThread уже после вызова слота специальным способом обрабатывает оставшиеся сообщения Код
Собственно специальная заплата самого Qt. |