Я писал
небольшой пост о том, как осуществляется удалённый сброс по питанию, но он
как-то слишком долго пишется. Так что пока я поделюсь
некоторыми соображениями на тему многопоточности в приложениях с 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 о том, что были получены некоторые данные.
No comments:
Post a Comment