Управление потоками в Qt

Я писал небольшой пост о том, как осуществляется удалённый сброс по питанию, но он как-то слишком долго пишется. Так что пока я поделюсь некоторыми соображениями на тему многопоточности в приложениях с UI. Предположим, что мы разрабатываем простое клиент-серверное приложение, от которого требуется вызывать некую callback-функцию каждый раз, когда приходят новые данные. Отсюда следует необходимость постоянного прослушивания порта, что невозможно реализовать в основном потоке, если есть и другие задания. Тут и пригождается многопоточность: мы создаём слушающий поток, в то время как главный ожидает команд от пользователя.

Существует два способа создания параллельных приложений на C++: на основе потоков и на основе задач, std::thread и std::async соответственно. Лично я предпочитаю std::thread по той простой причине, что std::async требует модификатора std::launch::async для полноценной отвязки потока. Хотя тут дело скорее в том, что я просто не оценил по достоинству все возможности std::async в действии.

Предположим, у нас есть некий класс:

class Foo {
private:
    std::thread RxThread;
    //…
    void InfiniteRead(std::function<void(uint8_t*, size_t &)> callback) {
          for (;;) {
                 //Read Some Data
                 if(read) {
                        callback(data, size_of_data);
                 }
          }
    }
    //…
public:
    void Init() {
        RxThread = std::thread(&Foo::InfiniteRead, this, callback);
    }
};

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

В Qt следует использовать event-ы для обновления графического интерфейса из другого (как бы) потока. Сначала, следует создать класс, который будет эти самые события обрабатывать.

class MyEvent : public QEvent{
public:
        struct event_msg{
            //some custom struct with data
        };   

    MyEvent(const event_msg& message) : QEvent(QEvent::User) {_message = message;}
    ~MyEvent() {}

    event_msg message() const {
        return _message;
    }

private:
    event_msg _message;
};

Далее объявляется простой метод в заголовочном файле класса графического интерфейса:

bool event(QEvent* event);

Этому методу соответствует следующая имплементация:

bool UI_Class::event(QEvent* event){
    if (event->type() == QEvent::User){
        MyEvent* postedEvent = static_cast<MyEvent*>(event);
        //some code
    }
    return QWidget::event(event);
}

В перегруженном методе обработки событий мы проверяем тип события и, если он совпадает с тем, что создали мы, производятся некоторые команда. Иначе событие отправляется дальше по пищевой цепочке. Ну и напоследок вкратце о том, как посылать эти события:

void blabla(){
    ...
    MyEvent::event_msg event;
    //fill event_msg with data
    MyEvent* e = new MyEvent(event);
    QCoreApplication::postEvent(parent, e);
}

Готово, вот так просто. Ещё очень хорошей идеей является уведомление потока через std::condition_variable о том, что были получены некоторые данные.


© 2022. All rights reserved.

Powered by Hydejack v9.1.6