Открыть список
Как стать автором
Обновить

Комментарии 17

В этом комментарии я буду выводить все изменения в тексте
Обнаружил ошибку в коде. Исправил
Написал новую статью по итогам обсуждения тут. Всех желающих приглашаю к прочтению!
Совершенно не понятно, зачем всё это может кому-то понадобиться в реальной жизни ;)
> работает, но как-то не так… Начинаю в режиме отладки отслеживать — а там творится черт знает что! То сигналы не выходят, то выходят, но как-то криво и из другого потока

Вообще непонятно почему у вас были проблемы.
Честно говоря, что-то я не проникся вашей идеей.
  • Наследовние от QThread требуется не так уж и часто, а если оно действительно нужно, то нет проблем создать объект внутри перегруженного метода run (текущий тред будет правильный), проинициализировать там все и правильно воспользоваться QEventLoop. Интерфейс внутренного объекта придется обернуть вызовами QMetaObject::invokeMethod(m_internalProcessor, "methodName", Qt::QueuedConnection);, что обезопасит всех пользователей от вопросов о синхронизации.
  • moveToThread требуется возможно еще реже, например, когда вы хотите иметь много объектов и процессить ивенты для всех объектов только в одном треде.
  • А вот что действительно нужно, так это для повседневных задач допилить QRunnable, чтобы использовать QFuture. К сожалению, QtConcurrent выглядит заброшенным, или просто ему приоритет понизили до самого низкого.
Я для работы использую такую схему:
1) Создаю класс, унаследованный от QThread;
2) Вкладываю в нем указатель ptr на класс, который я собираюсь использовать;
3)завожу переменную isStart типа bool, которая сигнализирует о инициализации класса в новом потоке.
4) примерно так переопределяю метод run():
void SomethingClass::run()
{
    ptr = new T;
    isStart = true;
    exec();
}

5) объявляю функцию, которая просто возвращает указатель ptr;

Данный подход хорош для случаев, когда есть происходит какая то однотипная обработка, осуществляемая силами одного класса.
Минусом же данного подхода является то, что валидность указателя ptr это головная боль программиста.
Не вижу практических причин создавать нечто вне потока исполнения, что бы затем перенести это в него.
С точки зрения практичности и эстетичность пользоваться методом run() как функцией, содержащую в себе исключительно пользовательский код для исполнения, возможно только при отсутствии метода exec(). Если требуется обработчик событий, то логично использовать классы, однако, инициализировать их объекты вне потока, по сути до его создания, по меньшей мере требует лишнего кода. Однако, на вкус и цвет все фломастеры разные.
Как-то вы перестарались. По-моему, нормальная тема — это делать вот так:
QThread *thread = new QThread(this):
Worker *job = new Worker;
job->moveToThread(thread):
thread->start();
job->do();

У меня такое решение в 100% случаев нормально работает, а когда не подходит, то я чаще всего прихожу к тому, что следует юзать QtConcurrent.
Спешу вас расстроить, но вызов job->do() запустить метод в текущем потоке, а не в потоке thread.
Я накидал небольшой код иллюстрирующий это
#include <QCoreApplication>
#include <QDebug>
#include <QThread>

class QSleep : public QThread
{
public:
    QSleep();
    static void qmsleep(int msec) {QThread::msleep(msec);}
};

class worker : public QObject
{
public:
    worker() {}
    void doIt();

};

void worker::doIt()
{
    while(true)
    {
        qDebug() << "I'm sleep now in new thread";
        QSleep::qmsleep(100);

    }
}
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QThread* th = new QThread();
    worker* petrovich = new worker;
    petrovich->moveToThread(th);
    th->start();
    petrovich->doIt();
    for(int i = 0 ; i < 100; i++)
    {
        qDebug() << "I'm sleep  in this thread";
        QSleep::qmsleep(1000);
    }

    return a.exec();
}


Тестировал на 4.7.4 win 8.1
Отпишитесь если будут другие результаты.
Если do() запускает внутри потока ивентлуп, то все будет окей. Никто же не будет в do() запихивать нагрузку :)
Я к тому, что весь код в do() будет выполняться в вызывающем его потоке. Это происходит потому, что функция moveToThread() отвязывает объект от обработчика очереди событий текущего потока и передает его новому. Поэтому запуск функции exec() внутри функции do() вообще то некорректен, так как обработчик очереди событий просто перезапустится, а не привяжется к новому потоку.
Поэтому если требуется вызвать метод класса, надо делать его слотом. Вам просто везло, что по умолчанию run() вызывает exec() и поток имеет очередь событий.
В журнале DrDobbs помню статью (к сожалению ссылку найти не могу), в которой показывались приемы работы с многопоточностью в Qt.
Одна из концепций такова (демонстрационный пример):
class MyThreadImpl
    : public QObject
{
    Q_OBJECT
public:
    MyThreadImpl();

public slots:
    void doStuff()
    {
        // impl...
        emit onSomeStuff();
        // impl
    }

signals:
    void onSomeStuff();
};

class MyThread
    : public QThread
{
    Q_OBJECT
public:
    void doStuff()
    {
        emit doStuffImpl();
    }

signals:
// external
    void onSomeStuff();

// internal
    void onStuffImpl();

private:

    void run()
    {
        MyThreadImpl threadImpl;

        // signal to thread
        connect(this, SIGNAL(onStuffImpl()), &threadImpl, SLOT(doStuff()));

        // signal from thread 
        connect(&threadImpl, SIGNAL(onSomeStuff()), this, SIGNAL(onSomeStuff()));

        exec();
    }
};

Со своей стороны нахожу данный подход одним из самых удачных для Qt и всем его рекомендую. Функции типа moveToThread считаю костылями, призванными исправлять существующие архитектурные косяки в проекте. Использовать их для новых решений ИМХО не стоит.
Аналогичный, более классический пример с использованием QEvent, который несколько более многословен, однако дает лучшую производительность.
Здесь как я понимаю получается следующие: MyThread используется как создатель нового потока, так и интерфейса для вложенного в него пользовательского класса. В принципе подход неплохой, за исключением двух Но:
1) так как объект QThread принадлежит потоку, в котором был создан, то сигнал придет сначала в него, а потом будет уже будет ретранслирован требуемому классу, то есть лишняя генерация сигнала. В принципе из-за того, что в одном потоке вызов сигнала приравнивается к непосредственному вызову соединенного с ним слота, издержками в большинстве случаев можно пренебречь.
2) Более существенная проблема состоит в том, что класс MyThread придется переписывать под каждый новый пользовательский класс, используемый в другом потоке.
Способ с QEvent довольно интересный, только я им подробно не занимался, поэтому сказать про него не могу.
1) Это да. Передача через event`ы эту проблему решает.
2) Я не считаю это проблемой, все равно интерфейс, определенный в MyThread будет привязан к конкретной задаче. Класс на практике называться будет соответственно этой задаче (а не MyThread). С другой стороны этот способ точно так же довольно просто поддаётся шаблонизации. Поэтому, если уже очень хочется, можно придумать нечто обобщённое на этой базе.

К слову сказать с более ранних версиях Qt event based межпоточное взаимодействие было основным способом. Т.к. во-первых поток не имел своей встроенной очереди сообщений, как в Qt4-Qt5, а во-вторых система сигналов не поддерживала межпоточные соединения. Приходилось делать свою очередь сообщений внутри run и проектировать систему событий для потока. Зато можно было передавать в основной поток события с помощью функции postEvent (собственно сейчас так тоже можно).
Согласен, так качественнее. Спасибо за идею!
Хотя я бы добавил startTimer(0), а в нем уже инициализацию нового объекта — тогда он будет работать с готовым циклом сообщений.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.