Название: Вопрос про слоты в потоке. Отправлено: kamil от Ноябрь 14, 2015, 19:01 Есть: Главный поток (он же поток GUI). Есть один Дополнительный поток.
Нужно: соединить сигнал из Главного потока и слот из Дополнительного потока с обработкой слота в Дополнительном потоке. Код: class Worker: public QObject { Код: Worker::Worker() { Сам экземпляр класса создается так: Код: int main(int argc, char *argv[]) { В результате слот startPause вызывается только при первом клике startPauseButton, то есть только до Код: moveToThread(&_thread); По идее после _thread.start(); должен запуститься event loop Дополнительного потока, и он должен обрабатывать сигналы. Но на деле как-то не очень. Где я ошибся? Название: Re: Вопрос про слоты в потоке. Отправлено: Bepec от Ноябрь 14, 2015, 19:30 Во 1 вы ошиблись, не выложив все файлы проекта. С каким то калькулятором которого тут нет вы связываете клик. У вас вообще ничего работать не должно в таком сеттинге :)
Во 2 перемещать надо как бы после запуска потока. Потому что ДО этого потока вообще нет. В 3 ваш слот process вызывает вопросы. В 4 вообще неясно взаимодействие данного класса с основным и как в результате смысл ваших действий не виден :) Название: Re: Вопрос про слоты в потоке. Отправлено: kamil от Ноябрь 14, 2015, 19:46 Да, сам класс достаточно большой, и назывался по-другому. Я отсек все лишнее, но не до конца, забыл класс переименовать в connect. Сейчас подправил.
Если сделать так: Код: void Worker::startPause() { И да, какие вопросы вызывает process()? Название: Re: Вопрос про слоты в потоке. Отправлено: Igors от Ноябрь 15, 2015, 08:58 Код: void Worker::process() { Код: connect( &_thread, &QThread::started, this, &Worker:: // process??? Вообще чего мудрите, чем не устраивает ф-ционал по умолчанию? Перенесите moveToThread в конструктор Worker'а, и придерживайтесь простой логики - Worker получает сигнал и отрабатывает его в слоте. Незачем городить свой loop и паузы если по умолчанию нитка сама засыпает без событий/сигналов. Нужно по кнопке остановить - делаете quit и wait. Запустить - делаете start. Название: Re: Вопрос про слоты в потоке. Отправлено: ssoft от Ноябрь 16, 2015, 08:43 Да, давно такого чуда не видел :D. Вообще метод moveToThread считаю вредным, о позволяет писать небрежный код и приводит к ошибкам, которые долго ищутся - с виду то все ок.
Например, сначала произвели подключение сигналов и слотов, получили DirectConnection, а затем сделали moveToThread и получили кучу проблем, так как вместо DirectConnection должно было быть QueredConnection. Это всего лишь простой пример, не говорю уже о проблемах с удалениями таких объектов и т.п. Предлагаю сделать так Код
Реализация Код
использование в коде Код
Название: Re: Вопрос про слоты в потоке. Отправлено: lit-uriy от Ноябрь 18, 2015, 07:18 Во 2 перемещать надо как бы после запуска потока. Потому что ДО этого потока вообще нет. Можно перемещать до запуска и это частое решение.Но в таком решении перемещать нужно самого Worker-а, а уже после старта потока пинать Worker-а (строго через слоты), чтобы он создал у себя зависимые объекты, тогда они будут жить в отдельном потоке. Название: Re: Вопрос про слоты в потоке. Отправлено: kamil от Ноябрь 19, 2015, 15:58 В общем из ответов я пока ничего конкретного не понял.
Код Да, тут действительно мой косяк, поправил. Цитировать Вообще чего мудрите, чем не устраивает ф-ционал по умолчанию? Перенесите moveToThread в конструктор Worker'а, и придерживайтесь простой логики - Worker получает сигнал и отрабатывает его в слоте. Незачем городить свой loop и паузы если по умолчанию нитка сама засыпает без событий/сигналов. Нужно по кнопке остановить - делаете quit и wait. Запустить - делаете start. Если перенести moveToThread в конструктор Worker'а, то не сработает слот startPause, ведь QThread::run, в котором крутиться QEventLoop, запускается только после вызова QThread::start. Не совсем понял где я вызывываю свой loop, а Код это пример просто некоторой периодической работы. И я не совсем понял, какой новый функционал я добавил, и что значит функционал по-умолчанию в этом случае. ssoft, я бы все-таки не хотел QThread наследовать. На сколько я понял, это нужно только если ты собираешься расширить возможности потоков Qt. Мне тут ничего такого не надо - только поток, который бы обрабатывал сигналы. Цитировать Но в таком решении перемещать нужно самого Worker-а А что в данном случае перемещает moveToThread? Я так понимаю если его вызвать из Worker'a как раз самого Worker'a он и переместит. Нет?Чтобы было поудобнее я сделал простенький проект, в котором реализован код, что в шапке. Вот ссылочка на него: https://drive.google.com/folderview?id=0By2N30gBjyToSFF1QzNBVXZMdVE&usp=sharing Коды: worker.h Код
worker.cpp Код
main.cpp Код
При нажатии на кнопку startPushButton Worker::start() вызывается, поток запускается (Worker::process()), но вот при нажатии на signalPushButton, слот Worker::slot() не запускается. Версия Qt 5.4.2 MinGW 32bit. Название: Re: Вопрос про слоты в потоке. Отправлено: Bepec от Ноябрь 19, 2015, 16:24 Наследование от QThread и movetothread это лишь варианты работы в отдельном потоке.
Тут надо судить проще - если вы в этот поток хотите засунуть 2+ воркера, тогда moveToThread. Если у вас один воркер - тогда проще наследоваться. Код: while(1) { Название: Re: Вопрос про слоты в потоке. Отправлено: kamil от Ноябрь 19, 2015, 16:43 Наследование от QThread и movetothread это лишь варианты работы в отдельном потоке. Тут надо судить проще - если вы в этот поток хотите засунуть 2+ воркера, тогда moveToThread. Если у вас один воркер - тогда проще наследоваться. Код: while(1) { Да, я похоже начал понимать в чем дело, капитан. Только осталось уточнить. Если у меня в потоке уже крутиться слот, то второй уже не вызвать, пока первый не закончит работу. Но если первый поток простаивает? Например, ждет в QThread::sleep() или QWaitCondition::wait(), второй все равно не запуститься? Название: Re: Вопрос про слоты в потоке. Отправлено: Bepec от Ноябрь 19, 2015, 17:37 Вы сначала определитесь что вам надо. Сформулируйте задачу, которая должна быть решена на выхлопе.
Вы вообще представляете как работает поток? Да, вызов слотов произойдёт последовательно в одном потоке. Для одновременной работы 2 слотов нужно чтобы они находились в разных потоках. Пока что у вас проскальзывает метод слепого тыка :) А надо лишь разобраться. PS вам надо собраться и сформулировать чего вы хотите просто на словах. А так вы пока не имеете ни теории ни практики использования потоков и порете код вслепую. Название: Re: Вопрос про слоты в потоке. Отправлено: ssoft от Ноябрь 19, 2015, 18:14 Именно, про ошибки такого рода я и писал
Код
Здесь соединяются сигналы и слоты способом Qt::DirectConnection. Вызов сигнала и далее вызов слота всегда будет в потоке вызвавшем &QPushButton::clicked, сколько бы мы не делали moveToThread для Worker. Таким образом запуск отдельного потока ничего не даст. Наследование от QThread не расширяет функциональность, а всего лишь вынужденная необходимость, как и наследование от QObject. При этом QThread сам является QObject и функционирует в потоке, в котором был создан. Метод run единственный метод QThread, который вызывается в отдельном потоке. Название: Re: Вопрос про слоты в потоке. Отправлено: kamil от Ноябрь 19, 2015, 18:15 Вы сначала определитесь что вам надо. Сформулируйте задачу, которая должна быть решена на выхлопе. Вы вообще представляете как работает поток? Да, вызов слотов произойдёт последовательно в одном потоке. Для одновременной работы 2 слотов нужно чтобы они находились в разных потоках. Пока что у вас проскальзывает метод слепого тыка :) А надо лишь разобраться. PS вам надо собраться и сформулировать чего вы хотите просто на словах. А так вы пока не имеете ни теории ни практики использования потоков и порете код вслепую. Да, у меня нет ни опыта работы с потоками ни теории. А цель такая: Нужно реализовать загрузчик прошивки по последовательному порту. Процесс примерно такой: 1. Выбор файла прошивки .hex, парсинг проверка прошивки, если ок, то активировать кнопку загрузки. 2. Ожидание нажатия кнопки загрузки пользователем. 3. Отправка команды прошивке на переход в режим загрузчика. 4. Ожидание сигнала готовности от загрузчика. 5. Отправка команды на очистку флеш памяти. 6. Ожидание окончания процесса очистки флеш памяти. 7. Отправка команды на запись блока. 8. Ожидание окончания процесса записи блока. 9. Goto 7 пока не закончится все блоки. 10. Отправка команды загрузки выхода из режима загрузчика. 11. Ожидание сигнала готовности от прошивки. Ожидания реализованы с использованием QWaitCondition::wait(). Соответственно чтобы запустить ожидающий поток нужно вызвать QWaitCondition::wakeAll(). QWaitCondition::wakeAll() вызывается из другого потока (главного), а доступ к объекту QWaitCondition синхронизируется через мютекс. Но я думал что можно будет вызывать QWaitCondition::wakeAll() из того же потока, что сидит в ожидании, тогда не надо будет беспокоится о синхронизации. Но теперь я понял в чем я был не прав. Мне почему-то казалось, что если Worker::process() ожидает, то почему бы ему не оброботать сигнал? Думал что после вызова QWaitCondition::wait() управление в потоке перейдет в его EventLoop. А вообще я был бы не против, если бы кто-нибудь скинул мне книгу, в которой работа с потоками будет раскрыта получше. Название: Re: Вопрос про слоты в потоке. Отправлено: Bepec от Ноябрь 19, 2015, 22:51 Ваш перепредыдущий комментарий неверен :)
Если отнаследоваться от QThread и в конструкторе сделать moveToThread(this), то мы получаем класс, который полностью существует в своём потоке. И никаких дополнительных телодвижений не надо :) Эммм... прочитав вашу задачу... А зачем вам собственно заморачиваться с мутексами и прочим? Сокращаем до: 1) форма с выбором файла и проверкой прошивки. Если ок кнопка доступна. Щелкаем, появляется прогрессбар. 2) поток общения с устройством, который реализует ваши пункты с 3 по 11. (ну и делаем ему сигнал аля "info", чтобы пользователь видел прогресс). 3) сообщаем пользователю что всё готово или ошибка, в зависимости от результатов. PS вам в данном случае не нужна синхронизация потоков, у вас их всего один и тот малофункциональный, просто чтоб интерфейс не вис :) Название: Re: Вопрос про слоты в потоке. Отправлено: kamil от Ноябрь 20, 2015, 02:06 Цитировать Ваш перепредыдущий комментарий неверен :) Если отнаследоваться от QThread и в конструкторе сделать moveToThread(this), то мы получаем класс, который полностью существует в своём потоке. И никаких дополнительных телодвижений не надо :) Да, теперь понял что он не верен. Цитировать Сокращаем до: 1) форма с выбором файла и проверкой прошивки. Если ок кнопка доступна. Щелкаем, появляется прогрессбар. 2) поток общения с устройством, который реализует ваши пункты с 3 по 11. (ну и делаем ему сигнал аля "info", чтобы пользователь видел прогресс). 3) сообщаем пользователю что всё готово или ошибка, в зависимости от результатов. Не совсем понятно тогда как организовать ожидание ответа от платы. Если что, обмен данными с платой происходит по последовательному порту - и объект QSerialPort принадлежит главному потоку (последовательный порт нужен не только загрузчику). А значит и сигнал эмитится в главном потоке. Поэтому и нужен мютекс - ведь в объект QWaitCondition может быть использован как из главного потока - сигнал об ответе платы - так и из вспомогательного потока - чтобы уснуть. Или я вас как-то не так понял? Название: Re: Вопрос про слоты в потоке. Отправлено: Bepec от Ноябрь 20, 2015, 03:16 И тут вы узко мыслите.
Вообще писать в ком порт может любой поток :/ (к примеру у меня в одном проектике по 2 потока на каждый порт, чтение/запись соответственно) Зачем основному потоку ком порт в момент загрузки прошивки? Ненужен. Потому или закрываем в основном, открываем в потоке, или же передаём хендл в поток и спокойно используем его там. Завершили - спокойно основным открываете /подхватываете. Делайте всё на сигналах. readyRead, там обработка приходящих, отправляете где угодно. PS это случай когда учебники не помогают. Qt упрощает работу с потоками, учебники заставляют вас писать всё с нуля. Хотя да, читайте учебники ПРО Qt, а не по чистому c++. Название: Re: Вопрос про слоты в потоке. Отправлено: lit-uriy от Ноябрь 20, 2015, 06:25 >>А что в данном случае перемещает moveToThread? Я так понимаю если его вызвать из Worker'a как раз самого Worker'a он и переместит. Нет?
Я полагаю, что некий объект живёт только в том потоке, в котором он был создан. т.е вызвав moveToThread для некого объекта, он всё же не переедет жить в другой поток, но объекты создаваемые внутри "мовнутого" и только после того как его "мовнули" буду созданны уже в отдельном потоке. П.С. в технических подробностях не шарю. Название: Re: Вопрос про слоты в потоке. Отправлено: ssoft от Ноябрь 20, 2015, 08:16 Ваш перепредыдущий комментарий неверен :) Если отнаследоваться от QThread и в конструкторе сделать moveToThread(this), то мы получаем класс, который полностью существует в своём потоке. И никаких дополнительных телодвижений не надо :) Наследование от QThread и вызов в конструкторе moveToThread(this) дает, конечно, необходимый результат, и при быстром решении конкретной задачи может быть использован. Я предложил решение, рассматривая задачу в общем виде (я же не знаю заранее, как будет использован Worker). В случае же наследования и использования moveToThread(this) существуют ограничения, о которых необходимо помнить Нельзя указывать родителя, иначе moveToThread не сработает. Цитата: Qt5.5 The object cannot be moved if it has a parent. Если объект без родителя создан в куче (через new), а не на стеке, то необходимо использовать соединение с deleteLater, что иногда является настоящим геморроем.Если объект создан на стеке, то его принудительное удаление может быть вызвано в момент его работы в другом потоке, что приведет к crash. Все эти нюансы могут быть неочевидны программистам с малым опытом работы с Qt, его QObject и QThread. Название: Re: Вопрос про слоты в потоке. Отправлено: Igors от Ноябрь 20, 2015, 10:12 А что в данном случае перемещает moveToThread? Я так понимаю если его вызвать из Worker'a как раз самого Worker'a он и переместит. Нет? У QObject есть поле/метод thread(), оно используется для определения в каком EventLoop он принимает сигналы. Никакого "перемещения" не происходит. Как работает QueuedConnection: - по thread получателя находит его EventLoop и кладет туда сигнал (событие) - когда поток освободится он полезет в этот EventLoop и извлечет оттуда событие (просто сработает слот приемника) Нужно реализовать загрузчик прошивки по последовательному порту. Зачем связываться с низкоуровневой синхронизацией? Просто лупите слот/сигналами, и все дела. Запустили нитку, она вошла в run - exec, стоит, ждет событий. Нажал юзер бубочку - послали сигнал, нитка его подхватила. Если она была чем-то занята - подхват случится после завершения этих занятий. Процесс примерно такой: 1. Выбор файла прошивки .hex, парсинг проверка прошивки, если ок, то активировать кнопку загрузки. 2. Ожидание нажатия кнопки загрузки пользователем. ... Ожидания реализованы с использованием QWaitCondition::wait(). .. Взаимный обмен/ожидания - тоже проще всего слот/сигналами. Что конкретно не выходит, чего не хватает (зачем лепите свой loop и wake)? Да, по QWaitCondition Верес у нас большой специалист :) Название: Re: Вопрос про слоты в потоке. Отправлено: Bepec от Ноябрь 20, 2015, 16:55 Ssoft пишет правильно, но чуть чуть неверно ;)
Все описанные им проблемы - невнимательность программиста и непродуманность архитектуры. Они в любом случае помешают проекту. |