LINUX.ORG.RU

сообщения между потоками

 , , ,


0

1

пытаюсь разобраться с многопоточностью Qt\C++
объясните, пожалйста, почему вот этот мой код работает не так, как задумывалось:

phyThread.h

#include <QThread>

class phyThread : public QThread {
    Q_OBJECT
public:
    void run();
signals:
    void count(int);
};

phyThread.cpp
#include "phyThread.h"

void phyThread::run() {
    int i = 0;
    while (i < 5) {
        emit count(i);
        i = i + 1;
        sleep(1000);
    }
}

phyView.h
#include <QtGui/QtGui>
#include "phyThread.h"

class phyView : public QGraphicsView {
    Q_OBJECT
    
public:
    phyView(QWidget *parent = 0);
    ~phyView();
protected:
    QGraphicsTextItem *text;
    phyThread *thread;
public slots:
    void changeCount(int);
};

phyView.cpp
#include "phyView.h"

phyView::phyView(QWidget *parent) : QGraphicsView(parent) {
    QGraphicsScene *scene = new QGraphicsScene(this);
    scene->setSceneRect(0, 0, 640, 480);
    setScene(scene);

    text = new QGraphicsTextItem();
    text->setPlainText("test");
    scene->addItem(text);

    thread = new phyThread();
    thread->moveToThread(thread);
    connect(thread, SIGNAL(count(int)), this, SLOT(changeCount(int)));
    thread->start();
}

phyView::~phyView() {
}

void phyView::changeCount(int i) {
    text->setPlainText(QString::number(i));
}

main.cpp
#include <QtGui/QApplication>
#include "phyView.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    phyView w;
    w.show();
    
    return app.exec();
}

стоит добавить, что кучу материала по теме я уже прочитал;
понятно, что сигнально\слотовые соединения между потоками внутри превращаются в события;
понятно, что тип соединения Qt::DirectConnection ни к чему хорошему в данном случае не приведёт;
thread->moveToThread(thread) - подобное решение нашёл в постах '08-09 годов на различных форумах; необходимо для того, чтобы слоты выполнялись в контексте моего потока
пробовал запускать в потоке свой цикл обработки событий, это ничего не изменило, оно и понятно: в потоке без своего цикла сигналы будут эмитироваться, но слоты работать не будут;

в общем, на деле здесь я получаю text с установленной строкой 0, хотя ожидаю последовательную смену значения от 0 до 4 с интервалом в секунду

Ответ на: You’re doing it wrong... от seed_stil

ещё раз, после прочтения статьи

phyCounter.h

#include <QObject>
#include "phyThread.h"

class phyCounter : public QObject {
    Q_OBJECT
public:
    phyCounter(QObject *parent = 0);
    void startCount();
signals:
    void count(int);
};

phyCounter.cpp
#include "phyCounter.h"

phyCounter::phyCounter(QObject *parent) : QObject(parent) {
}

void phyCounter::startCount() {
    int i = 0;
    while (i < 5) {
        emit count(i);
        i = i + 1;
        dynamic_cast<phyThread*>(thread())->phySleep(1000);
    }
}

phyThread.h
#include <QThread>

class phyThread : public QThread {
    Q_OBJECT
public:
    phyThread(QObject *parent = 0);
    void phySleep(int msec);
};

phyThread.cpp
#include "phyThread.h"

phyThread::phyThread(QObject *parent) : QThread(parent) {
}

void phyThread::phySleep(int msec) {
    msleep(msec);
}

phyView.h
#include <QtGui/QtGui>
#include <QtOpenGL/QtOpenGL>
#include "phyCounter.h"

class phyView : public QGraphicsView {
    Q_OBJECT
    
public:
    phyView(QWidget *parent = 0);
    ~phyView();
protected:
    QGraphicsTextItem *text;
    phyThread *thread;
public slots:
    void changeCount(int);
};

phyView.cpp
#include "phyView.h"

phyView::phyView(QWidget *parent) : QGraphicsView(parent) {
    QGraphicsScene *scene = new QGraphicsScene(this);
    scene->setSceneRect(0, 0, 640, 480);
    setScene(scene);

    text = new QGraphicsTextItem();
    text->setPlainText("test");
    scene->addItem(text);

    thread = new phyThread();
    thread->start();

    phyCounter *counter = new phyCounter();
    counter->moveToThread(thread);
    connect(counter, SIGNAL(count(int)), this, SLOT(changeCount(int)));
    counter->startCount();
}

phyView::~phyView() {
}

void phyView::changeCount(int i) {
    text->setPlainText(QString::number(i));
}

уже ближе к истине, но окно graphicsView появляется, судя по всему, только после завершения цикла while() в моём потоке

vladimirsmirnov9
() автор топика
Ответ на: комментарий от vladimirsmirnov9

phyCounter.cpp:11 это выполняется в контексте потока, который за графику отвечает. Когда while заканчивается, поток получает 4 ивента, быстренько всё меняет и потом только окошко показывает. Делай в phyCounter protected void timerEvent и emit count оттуда. А в конструкторе startTimer(1000).

nanoolinux ★★★★
()
Ответ на: комментарий от nanoolinux

phyCounter.cpp:11 это выполняется в контексте потока, который за графику отвечает.

я это понял, когда решил дождаться сигнала started() и в слоте создавал phyCounter. интерфейс зависал на эти пресловутые 5 секунд, а вызов viewport->repaint() выполнялся.
но всё-таки - почему код выполняется в главном потоке?

Usually, this means simply changing your class to inherit from QObject instead of QThread and, possibly, changing the class name

...

To actually have your code run in the new thread context, you need to instantiate a QThread and assign your object to that thread using the moveToThread() function.

ведь так и сделал!

vladimirsmirnov9
() автор топика
Ответ на: комментарий от vladimirsmirnov9

counter->startCount();

Давайте зададим себе вопрос: отчего и почему, каким таким магическим образом вызов ф-ии в с должен выполняться в другом потоке?

nanoolinux ★★★★
()
Ответ на: комментарий от nanoolinux

Давайте зададим себе вопрос: отчего и почему, каким таким магическим образом вызов ф-ии в с должен выполняться в другом потоке?

а в чём тогда смысл перемещения QObject'а в другой поток?

vladimirsmirnov9
() автор топика
Ответ на: комментарий от vladimirsmirnov9

в том, что ивенты и слоты отрабатывают в другом потоке и графика при этом не дёргается. еверисинх смус энд клин.

nanoolinux ★★★★
()
Ответ на: комментарий от vladimirsmirnov9

pewpewpew

QTimer::singleShot(counter, SLOT(startCount()))
QMetaObject::invokeMethod(counter, «startCount»)

anonymous
()
Ответ на: комментарий от vladimirsmirnov9

чесно говоря я не понимаю почему оно ждет завершения функции.
но костыль очень простой: пометь startCount как Q_INVOKABLE и из конструктора запускай его через QMetaObject::InvokeMethod(counter, «startCount»)

seed_stil ★★
()
Ответ на: комментарий от vladimirsmirnov9

Суть в том, что когда ты вызываешь функцию, она обрабатывается в том потоке, в котором ты ее вызываешь.
Сигналы и слоты «сами» находят нужный поток. Если хочешь узнать как, смотри исходники кутэ. Я предполагаю, что они кладут нужный эвент в eventloop целевого потока, и так как eventloop находится в другом потоке, то слоты которые eventloop вызывает по информации из эвентов уже исполняются в этом самом потоке.

anonymous
()
Ответ на: комментарий от vladimirsmirnov9

да, ты прав, я перепутал. только не забудь про exec() в конце.

nanoolinux ★★★★
()
Ответ на: комментарий от seed_stil

да, это работает :)

Суть в том, что когда ты вызываешь функцию, она обрабатывается в том потоке, в котором ты ее вызываешь.

но почему? логично было бы предположить, что если объект находится в другом потоке, то и его методы будут обрабатываться там же

Q_INVOKABLE

расскажите подробнее про этот макрос, пожалуйста

vladimirsmirnov9
() автор топика
Ответ на: комментарий от vladimirsmirnov9

Без костылей никак. У тебя startCounter сигналы эмитит, т.е. надо что бы exec() уже работал, но он ещё не работает, но перед startCounter его не поставишь, потому-что он не возращается. костыль смотри тебе товарищ seed_stil подсказывает.

можете здесь так не принято.

nanoolinux ★★★★
()
Ответ на: комментарий от vladimirsmirnov9

но почему? логично было бы предположить, что если объект находится в другом потоке, то и его методы будут обрабатываться там же

Вы, товарищ, видимо далеки от понимания что такое поток исполнения и как он работает.

erfea ★★★★★
()
Ответ на: комментарий от vladimirsmirnov9

но почему? логично было бы предположить, что если объект находится в другом потоке, то и его методы будут обрабатываться там же

Потому что исполнение программы находится в текущем потоке. Чтобы как-то вызвать функцию в другом потоке, пришлось бы как-то переключаться на этот другой поток. К тому же, у произвольного объекта или функции в С++ нет никакой информации о том, какому потоку они принадлежат. У QObject'а есть такая инфа, но вызов метода это не кутэшная фича, сигналы и слоты - кутэшная.

anonymous
()
Ответ на: комментарий от vladimirsmirnov9

Q_INVOKABLE

расскажите подробнее про этот макрос, пожалуйста

это не макрос, это флаг для moc'а
регистрирует метод в кутешной MetaObject system

seed_stil ★★
()
Ответ на: комментарий от erfea

ну да, далёк. ради чего я это затеял - хочу вынести физический движок в отдельный поток. физика должна рассчитываться с постоянной частотой, сигналы-события с аргументами, содержащими новую инфу (положение+наклон) будут сыпаться назад в гуй, который будет перерисовываться с переменной частотой, а в поток с физикой, с гораздо меньше частотой, буду засылать только текущий rect от сцены, чтобы обновлять только те тела, которые видны на экране. просто у меня ощущение, что циклы со слипами всяко будут работать быстрее, чем таймеры (которые, возможно, внутри так же и устроены)

vladimirsmirnov9
() автор топика
Ответ на: комментарий от vladimirsmirnov9

Есть еще один костыль: QCoreApplication::processEvents

anonymous
()
Ответ на: комментарий от vladimirsmirnov9

Ну собственно для начала поситай что такое QObject, врубись для начала что он несёт под копотом. Отдели понятие QThred от thred. Не складывай в одну кучу мух и котлеты. И да таки потоку пох на то к какому потоку ты отнёс в Qt тот или иной объект, он тупо перемалывает все подвернувшиеся инструкции.

erfea ★★★★★
()

а вообще, движок и гуй объединять - это моветон на вашем месте я бы сделал следующим образом:

class phyThread : public QThread {
//logic
};

class phyView : public QGraphicsView {
//gui
};

int main(int argc; char ** argv){
    QApplication app(argc, argv);
    phyThread engine;
    phyView gui;
    QObject::connect(&engine, SIGNAL(count(int)), &gui, SLOT(changeCount(int)), Qt::QueuedConnection);
    gui.show();
    engine.start();
    return app.exec();
}

Проще говоря, ошибка в дизайне, если правильно проектировать, то и костыли не нужны, которых тут вам успели уже насоветовать.

EugeneBas ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.