Многопоточность в Qt Widgets +1


При работе приложения с оконным интерфейсом важно обеспечить отсутствие зависаний. Для этого сложные вычисления стоит запускать в отдельной нити. Концепция многопоточного приложения отлично сочетается с подходом сигналы-слоты Qt, при этом совершенно не нужно переопределять никакой метод run().

Основная идея. В многопоточном приложении вычисления проводятся в отдельной ните, по окончанию излучается сигнал, передающий результат в своих аргументах. Слот, принадлежащий уже MainWindow, будет вызван. Результаты вычислений окажутся в аргументах слота и не составит труда вывести их.

Проводя аналогию с микроконтроллерами, сигнал — это переход по вектору прерывания, а слот — сам обработчик прерывания. Чтобы возникло «прерывание», сигнал необходимо излучить: emit mysignalvoid(); тогда Qt начнёт искать ему «обработчик» (слот). В Qt можно на один сигнал повесить много слотов.

На следующем рисунке представлен алгоритм работы демонстрационного приложения. Нить обработки ежесекундно опрашивает по легенде USB HID устройство. Затем излучается (emit) сигнал, который обрабатывает слот, принадлежащий уже MainWindow и имеющий соответственно возможность рисовать на форме.



Итак, имеем классы, способные использовать подход сигналы-слоты. Для этого в их объявлении используется макрос Q_OBJECT.

class Worker : public QObject
{
    Q_OBJECT  //теперь можем использовать сигналы-слоты в классе

public:
    QTimer *timerDeviceRead;  //будем каждую секунду вызывать собственный слот GuiUpdateCallback
    Worker();

public slots:
    void updateElectropipData();

signals:
    void GuiUpdatePlease(uint8_t const *arrptr,size_t);
};

class MainWindow : public QMainWindow  //класс окна с GUI
{
    Q_OBJECT  
public slots:
    void GuiUpdateCallback(uint8_t const *arrptr, size_t);
private:
    Ui::MainWindow *ui;  //доступ к кнопкам и иже с ними
    QThread *thread;

Чтобы передавать результат вычислений в аргументах необходимо зарегистрировать типы, используемые для аргументов. Сделать это можно в разных местах, но обычно — в конструкторе класса, который потом будет с этими типами работать:

Worker::Worker(){
    qRegisterMetaType<std::size_t>("size_t");  
    qRegisterMetaType<uint8_t const *>("uint8_t const *");


Далее в конструкторе нити обработки создаётся таймер. Каждую секунду будет вызываться слот обработки updateUSBDataCallback. Обратите внимания на синтаксис подключения сигнал-слот в Qt5.

    this->timerDeviceRead = new QTimer();
    connect(Worker::timerDeviceRead, &QTimer::timeout, this, &Worker::updateUSBDataCallback);
    this->timerDeviceRead->start();

Ниже приведено тело слота нити обработки. Здесь должен находится весь долгодумающий код.

void Worker::updateUSBDataCallback(){
    size_t mysize = 65;
    uint8_t buf[65] = { "I like USB HID" };
    emit GuiUpdatePlease(buf,mysize);  //излучение сигнала
	//Подмена:
    for(int i =0;i<64;i++){
        buf[i]=i+'0';
        if (i+'0' > 250) i=0;
    }
}

Для демонстрации здесь сразу после излучения сигнала к слоту MainWindow нагло модифицируется содержимое массива. А так как массив передаётся по указателю, получается грязное чтение. Для предотвращения подобной ситуации сигнал из нити обработки должен быть связан со слотом GuiUpdateCallback() определённым образом:

MainWindow::MainWindow{
connect(worker, &Worker::GuiUpdatePlease, this, &MainWindow::GuiUpdateCallback, Qt::BlockingQueuedConnection);

В таком случае, излучив сигнал, нить обработки блокируется до окончания работы слота GuiUpdateCallback().

Если в тексте программы вас смущает длинное «uint8_t const *», то можно завести синоним TU8PTR:

typedef uint8_t const * TU8PTR;

Исходный код




К сожалению, не доступен сервер mySQL