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

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

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Вопрос про слоты в потоке.  (Прочитано 15283 раз)
kamil
Гость
« : Ноябрь 14, 2015, 19:01 »

Есть: Главный поток (он же поток GUI). Есть один Дополнительный поток.
Нужно: соединить сигнал из Главного потока и слот из Дополнительного потока с обработкой слота в Дополнительном потоке.

Код:
class Worker: public QObject {
Q_OBJECT

public:
                        Worker();
                       ~Worker();

private slots:
    void startPause();
    void process();

private:
    QThread             _thread;
};
Код:
Worker::Worker() {
    connect( &_thread, &QThread::started, this, &Worker::process );
    connect( MainWindow::ui().startPauseButton, &QPushButton::clicked, this, &Worker::startPause);
}

Worker::~Worker() {}

void Worker::process() {
    while(1) {
        // useful commands
        QThread::usleep( 100 );
    }
}

void Worker::startPause() {
    qDebug() << "s";
    if( !_thread.isRunning() ) {
        moveToThread(&_thread);
        _thread.start();
    }
}

Сам экземпляр класса создается так:
Код:
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;

w.show();

Worker worker;

return a.exec();
}


В результате слот startPause вызывается только при первом клике startPauseButton, то есть только до
Код:
moveToThread(&_thread);
. После перемещения этот слот уже не вызывается. Работает только если коннектить с Qt::DirectConnection.

По идее после _thread.start(); должен запуститься event loop Дополнительного потока, и он должен обрабатывать сигналы. Но на деле как-то не очень.

Где я ошибся?
« Последнее редактирование: Ноябрь 17, 2015, 12:52 от kamil » Записан
Bepec
Гость
« Ответ #1 : Ноябрь 14, 2015, 19:30 »

Во 1 вы ошиблись, не выложив все файлы проекта. С каким то калькулятором которого тут нет вы связываете клик. У вас вообще ничего работать не должно в таком сеттинге Улыбающийся

Во 2 перемещать надо как бы после запуска потока. Потому что ДО этого потока вообще нет.

В 3 ваш слот process вызывает вопросы.

В 4 вообще неясно взаимодействие данного класса с основным и как в результате смысл ваших действий не виден Улыбающийся
Записан
kamil
Гость
« Ответ #2 : Ноябрь 14, 2015, 19:46 »

Да, сам класс достаточно большой, и назывался по-другому. Я отсек все лишнее, но не до конца, забыл класс переименовать в connect. Сейчас подправил.

Если сделать так:
Код:
void Worker::startPause() {
    qDebug() << "s";
    if( !_thread.isRunning() ) {
        _thread.start();
        moveToThread(&_thread);
    }
}
то есть переместить перемещать экземпляр класса Worker в поток после его старта не помогает. Сигналы все равно не доходят.

И да, какие вопросы вызывает process()?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Ноябрь 15, 2015, 08:58 »

Код:
void Worker::process() {
    while(1) {
        // useful commands
        QThread::usleep( 100 );
    }
}
Не вижу каким образом этот код получит управление. Возможно Вы не дописали это здесь

Код:
 connect( &_thread, &QThread::started, this, &Worker::  // process???
Ну тогда да, сигналы и не придут, т.к. до опроса EventLoop дело не доходит.

Вообще чего мудрите, чем не устраивает ф-ционал по умолчанию? Перенесите moveToThread в конструктор Worker'а, и придерживайтесь простой логики - Worker получает сигнал и отрабатывает его в слоте. Незачем городить свой loop и паузы если по умолчанию нитка сама засыпает без событий/сигналов. Нужно по кнопке остановить - делаете quit и wait. Запустить - делаете start.
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #4 : Ноябрь 16, 2015, 08:43 »

Да, давно такого чуда не видел  Веселый. Вообще метод moveToThread считаю вредным, о позволяет писать небрежный код и приводит к ошибкам, которые долго ищутся - с виду то все ок.
Например, сначала произвели подключение сигналов и слотов, получили DirectConnection, а затем сделали moveToThread и получили кучу проблем, так как вместо DirectConnection должно было быть QueredConnection. Это всего лишь простой пример, не говорю уже о проблемах с удалениями таких объектов и т.п.

Предлагаю сделать так

Код
C++ (Qt)
 
class Worker
   : public QObject
{
   Q_OBJECT
 
   // ...
 
   Q_SLOT void processSlot ();
};
 
 
class WorkThread
   : public QThread
{
   Q_OBJECT
 
private:
   Worker m_worker;
 
public:
   WorkThread ( QObject * const parent = 0 );
 
protected:
   virtual void run () final;
 
public:
   Q_SIGNAL void processSlot ();
};
 
 

Реализация

Код
C++ (Qt)
 
void Worker::processSlot ()
{
   // my process realization
};
 
void WorkThread::run ()
{
   Worker worker;
   connect( this, &WorkThread::processSlot, &worker, &Worker::processSlot );
   exec();
}
 
 

использование в коде

Код
C++ (Qt)
 
void foo ()
{
   // ...
   WorkThread thread;
   connect( &invoker, &processInvoked, thread, &processSlot );
   thread.start();
   // ...
};
 
 

Записан
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #5 : Ноябрь 18, 2015, 07:18 »

Во 2 перемещать надо как бы после запуска потока. Потому что ДО этого потока вообще нет.
Можно перемещать до запуска и это частое решение.
Но в таком решении перемещать нужно самого Worker-а, а уже после старта потока пинать Worker-а (строго через слоты), чтобы он создал у себя зависимые объекты, тогда они будут жить в отдельном потоке.
Записан

Юра.
kamil
Гость
« Ответ #6 : Ноябрь 19, 2015, 15:58 »

В общем из ответов я пока ничего конкретного не понял.

Код
C++ (Qt)
connect( &_thread, &QThread::started, this, &Worker::  // process???
 
Да, тут действительно мой косяк, поправил.

Цитировать
Вообще чего мудрите, чем не устраивает ф-ционал по умолчанию? Перенесите moveToThread в конструктор Worker'а, и придерживайтесь простой логики - Worker получает сигнал и отрабатывает его в слоте. Незачем городить свой loop и паузы если по умолчанию нитка сама засыпает без событий/сигналов. Нужно по кнопке остановить - делаете quit и wait. Запустить - делаете start.

Если перенести moveToThread в конструктор Worker'а, то не сработает слот startPause, ведь QThread::run, в котором крутиться QEventLoop, запускается только после вызова QThread::start.
Не совсем понял где я вызывываю свой loop, а
Код
C++ (Qt)
while(1) {
       // useful commands
       QThread::usleep( 100 );
}
 
это пример просто некоторой периодической работы.
И я не совсем понял, какой новый функционал я добавил, и что значит функционал по-умолчанию в этом случае.


ssoft, я бы все-таки не хотел QThread наследовать. На сколько я понял, это нужно только если ты собираешься расширить возможности потоков Qt. Мне тут ничего такого не надо - только поток, который бы обрабатывал сигналы.


Цитировать
Но в таком решении перемещать нужно самого Worker-а
А что в данном случае перемещает moveToThread? Я так понимаю если его вызвать из Worker'a как раз самого Worker'a он и переместит. Нет?

Чтобы было поудобнее я сделал простенький проект, в котором реализован код, что в шапке. Вот ссылочка на него: https://drive.google.com/folderview?id=0By2N30gBjyToSFF1QzNBVXZMdVE&usp=sharing
Коды:
worker.h
Код
C++ (Qt)
class Worker : public QObject {
Q_OBJECT
public:
Worker(QObject *parent = 0);
~Worker();
public slots:
void slot();
private slots:
void start();
void process();
private:
QThread _thread;
};
 

worker.cpp
Код
C++ (Qt)
Worker::Worker(QObject *parent) : QObject(parent) {
connect( &_thread, &QThread::started, this, &Worker::process );
connect( MainWindow::ui().startPushButton, &QPushButton::clicked, this, &Worker::start );
connect( MainWindow::ui().signalPushButton, &QPushButton::clicked, this, &Worker::slot );
}
 
Worker::~Worker() {}
 
void Worker::start() {
_thread.start();
moveToThread(&_thread);
qDebug() << "Worker::start()";
}
 
void Worker::process() {
qDebug() << "Worker::process()";
while(1) {
;
}
}
 
void Worker::slot() {
qDebug() << "Worker::slot()";
}
 

main.cpp
Код
C++ (Qt)
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
Worker worker;
w.show();
return a.exec();
}
 

При нажатии на кнопку startPushButton Worker::start() вызывается, поток запускается (Worker::process()), но вот при нажатии на signalPushButton, слот Worker::slot() не запускается.
Версия Qt 5.4.2 MinGW 32bit.
« Последнее редактирование: Ноябрь 19, 2015, 16:05 от kamil » Записан
Bepec
Гость
« Ответ #7 : Ноябрь 19, 2015, 16:24 »

Наследование от QThread и movetothread это лишь варианты работы в отдельном потоке.

Тут надо судить проще - если вы в этот поток хотите засунуть 2+ воркера, тогда moveToThread.
Если у вас один воркер - тогда проще наследоваться.

Код:
while(1) {
;
}
Этот код убьёт ваш поток. Ваш Кэп.
Записан
kamil
Гость
« Ответ #8 : Ноябрь 19, 2015, 16:43 »

Наследование от QThread и movetothread это лишь варианты работы в отдельном потоке.

Тут надо судить проще - если вы в этот поток хотите засунуть 2+ воркера, тогда moveToThread.
Если у вас один воркер - тогда проще наследоваться.

Код:
while(1) {
;
}
Этот код убьёт ваш поток. Ваш Кэп.

Да, я похоже начал понимать в чем дело, капитан.
Только осталось уточнить. Если у меня в потоке уже крутиться слот, то второй уже не вызвать, пока первый не закончит работу. Но если первый поток простаивает? Например, ждет в QThread::sleep() или QWaitCondition::wait(), второй все равно не запуститься?

Записан
Bepec
Гость
« Ответ #9 : Ноябрь 19, 2015, 17:37 »

Вы сначала определитесь что вам надо. Сформулируйте задачу, которая должна быть решена на выхлопе.

Вы вообще представляете как работает поток?
Да, вызов слотов произойдёт последовательно в одном потоке. Для одновременной работы 2 слотов нужно чтобы они находились в разных потоках.

Пока что у вас проскальзывает метод слепого тыка Улыбающийся А надо лишь разобраться.

PS вам надо собраться и сформулировать чего вы хотите просто на словах. А так вы пока не имеете ни теории ни практики использования потоков и порете код вслепую.
« Последнее редактирование: Ноябрь 19, 2015, 17:41 от Bepec » Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #10 : Ноябрь 19, 2015, 18:14 »

Именно, про ошибки такого рода я и писал

Код
C++ (Qt)
 
Worker::Worker(QObject *parent) : QObject(parent) {
connect( &_thread, &QThread::started, this, &Worker::process );
connect( MainWindow::ui().startPushButton, &QPushButton::clicked, this, &Worker::start );
connect( MainWindow::ui().signalPushButton, &QPushButton::clicked, this, &Worker::slot );
}
 
 

Здесь соединяются сигналы и слоты способом Qt::DirectConnection. Вызов сигнала и далее вызов слота всегда будет в потоке вызвавшем &QPushButton::clicked, сколько бы мы не делали moveToThread для Worker. Таким образом запуск отдельного потока ничего не даст.

Наследование от QThread не расширяет функциональность, а всего лишь вынужденная необходимость, как и наследование от QObject.
При этом QThread сам является QObject и функционирует в потоке, в котором был создан. Метод run единственный метод QThread, который вызывается в отдельном потоке.
Записан
kamil
Гость
« Ответ #11 : Ноябрь 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.

А вообще я был бы не против, если бы кто-нибудь скинул мне книгу, в которой работа с потоками будет раскрыта получше.
Записан
Bepec
Гость
« Ответ #12 : Ноябрь 19, 2015, 22:51 »

Ваш перепредыдущий комментарий неверен Улыбающийся

Если отнаследоваться от QThread и в конструкторе сделать moveToThread(this), то мы получаем класс, который полностью существует в своём потоке. И никаких дополнительных телодвижений не надо Улыбающийся


Эммм... прочитав вашу задачу... А зачем вам собственно заморачиваться с мутексами и прочим?
Сокращаем до:
1) форма с выбором файла и проверкой прошивки. Если ок кнопка доступна. Щелкаем, появляется прогрессбар.
2) поток общения с устройством, который реализует ваши пункты с 3 по 11. (ну и делаем ему сигнал аля "info", чтобы пользователь видел прогресс).
3) сообщаем пользователю что всё готово или ошибка, в зависимости от результатов.

PS вам в данном случае не нужна синхронизация потоков, у вас их всего один и тот малофункциональный, просто чтоб интерфейс не вис Улыбающийся
Записан
kamil
Гость
« Ответ #13 : Ноябрь 20, 2015, 02:06 »

Цитировать
Ваш перепредыдущий комментарий неверен  Улыбающийся

Если отнаследоваться от QThread и в конструкторе сделать moveToThread(this), то мы получаем класс, который полностью существует в своём потоке. И никаких дополнительных телодвижений не надо  Улыбающийся

Да, теперь понял что он не верен.

Цитировать
Сокращаем до:
1) форма с выбором файла и проверкой прошивки. Если ок кнопка доступна. Щелкаем, появляется прогрессбар.
2) поток общения с устройством, который реализует ваши пункты с 3 по 11. (ну и делаем ему сигнал аля "info", чтобы пользователь видел прогресс).
3) сообщаем пользователю что всё готово или ошибка, в зависимости от результатов.

Не совсем понятно тогда как организовать ожидание ответа от платы. Если что, обмен данными с платой происходит по последовательному порту - и объект QSerialPort принадлежит главному потоку (последовательный порт нужен не только загрузчику). А значит и сигнал эмитится в главном потоке. Поэтому и нужен мютекс - ведь в объект QWaitCondition может быть использован как из главного потока - сигнал об ответе платы - так и из вспомогательного потока - чтобы уснуть.

Или я вас как-то не так понял?
Записан
Bepec
Гость
« Ответ #14 : Ноябрь 20, 2015, 03:16 »

И тут вы узко мыслите.
Вообще писать в ком порт может любой поток :/ (к примеру у меня в одном проектике по 2 потока на каждый порт, чтение/запись соответственно)

Зачем основному потоку ком порт в момент загрузки прошивки? Ненужен.
Потому или закрываем в основном, открываем в потоке, или же передаём хендл в поток и спокойно используем его там.
Завершили - спокойно основным открываете /подхватываете.

Делайте всё на сигналах. readyRead, там обработка приходящих, отправляете где угодно.

PS это случай когда учебники не помогают. Qt упрощает работу с потоками, учебники заставляют вас писать всё с нуля. Хотя да, читайте учебники ПРО Qt, а не по чистому c++.
« Последнее редактирование: Ноябрь 20, 2015, 03:19 от Bepec » Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


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