Russian Qt Forum
Май 02, 2024, 23:33 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3 ... 5   Вниз
  Печать  
Автор Тема: Как разрулить потоки  (Прочитано 25374 раз)
Sphynx
Гость
« : Июль 25, 2016, 15:50 »

Доброго дня. Проблема такая: приложение периодически по таймеру отправляет запрос и читает ответ от устройства подключенного по ком-порту используя QtSerialPort. Для приема сообщений написана отдельная функция read_msg(). Чтобы приложение на зависало при ожидании сообщения используется еще один таймер-таймаут при срабатывании которого функция сообщит что данные с порта не пришли. А чтобы работал этот таймер я вставил в цикл функции QApplication::processEvents();. Все работало нормально, но понадобилось отправлять устройству не только запросы по таймеру, но еще и по нажатию кнопок на форме. И происходит вот что: запрос по таймеру вызывает функцию read_msg(), функция запускает QApplication::processEvents(); и если в этот момент произойдет нажатие на кнопку, то произойдет еще один запрос устройству, и еще один вызов read_msg() и нарушается порядок чтения ответов. Поставил внутри функции read_msg() флаг занятости который сбрасывается после получения ответа, а в самом начале:
Код
C++ (Qt)
   while(ack==false)
   {//ERROR !!!
       Sleep(1);
       //QApplication::processEvents();
   }
 
Если остается Sleep, то приложение зависает намертво, если QApplication::processEvents(); , то перестают работать запросы по таймеру, видимо происходит какое-то переполнение в недрах самого qt.
Я так понимаю что запросы лезут с разных потоков, но не понятно как дать нормально отработать тому кто первый обратился к функции
read_msg()
Записан
Bepec
Гость
« Ответ #1 : Июль 25, 2016, 16:35 »

1 поток на чтение/запись, отправка приём через сигнал-слотовые соединения. И никаких проблем.
Хотя тут уже от вашего протокола зависит )
Записан
Sphynx
Гость
« Ответ #2 : Июль 25, 2016, 16:57 »

Не понятно как это решит проблему. Ведь функция read_msg() видоизменится, но все-равно останется. И значит она так-же может быть вызвана одновременно несколько раз. Все таки должен быть какой-то механизм принудительного переключения потоков. И пока один поток "держит" функцию, другие должны просто подождать.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #3 : Июль 25, 2016, 17:00 »

Не понятно как это решит проблему. Ведь функция read_msg() видоизменится, но все-равно останется. И значит она так-же может быть вызвана одновременно несколько раз. Все таки должен быть какой-то механизм принудительного переключения потоков. И пока один поток "держит" функцию, другие должны просто подождать.
Не нужны здесь несколько потоков. QSerialPort умеет работать асинхронно.
Организуйте очередь запросов и не посылайте новый запрос в железку, пока не получите ответ или не сработает timeout на старый. Все это будет спокойно асинхронно работать даже в GUI-потоке.
Записан
Sphynx
Гость
« Ответ #4 : Июль 25, 2016, 17:07 »

Да, это решит проблему, но передача тогда будет по сути полудуплексной, т.е. устройство на другой стороне уже будет готово принять новую команду для обработки, но придется ждать пока не будет получен ответ от предыдущей.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #5 : Июль 25, 2016, 17:09 »

Да, это решит проблему, но передача тогда будет по сути полудуплексной, т.е. устройство на другой стороне уже будет готово принять новую команду для обработки, но придется ждать пока не будет получен ответ от предыдущей.
Ну так вы же этого и добиваетесь?

то произойдет еще один запрос устройству, и еще один вызов read_msg() и нарушается порядок чтения ответов.
Записан
Sphynx
Гость
« Ответ #6 : Июль 25, 2016, 17:26 »

Сейчас попытаюсь объяснить. Вот допустим периодически по таймеру мы просим устройство - скажи параметры 1 2, устройство честно отвечает и скидывает их в свой буфер для передачи и потихоньку отправляет по каналу, ожидая новых запросов. В программе на ПК мы вызываем read_msg() и ждем пока не получим весь ответ. Но вот пользователь нажал на кнопку и из другого потока передал устройству новую команду - скажи параметры 3 4 и соответственно снова вызывается read_msg() в ожидании ответа. В буфере у нас очевидно окажется п1 п2 п3 п4, и необходимо придержать второй поток пока первый поток не заберет свои п1 п2. Так вот как дать первому потоку это сделать ? Как на него переключиться принудительно.
Записан
Bepec
Гость
« Ответ #7 : Июль 25, 2016, 17:50 »

Вот тут как раз уже нужен ваш протокол.
1) можно просто отсылать все ответы всем потокам (сигнал прицепить, а там пусть разбирается кто и что отправлял).
2) можно написать менеджер-надстройку, который будет контролировать запросы, ответы, кто что запросил и кому что отправлять.
Остальные варианты экзотичны или безумны.

Если у вас поток данных небольшой, вы можете спокойно сделать по п.1.
Если же вам нужна конкретика что/кто запросил, что/кто получил с контролем ошибок - пишите менеджер.

update: немного вам упрощу на вашем же примере.
По п.1
В слоте readyRead вы забираете данные и отправляете всем потокам пришедшие параметры (1,2,3,4)
Поток 1 смотрит в список запросов, сравнивает там параметр 1,2 и работает с ними. Параметры 3,4 игнорирует, ибо не запрашивал.
Поток 2 аналогично, только игнорирует 1,2.

По п.2
У вас имеется менеджер, куда вы передаёте информация
Поток 1 запросил параметры 1,2
Поток 2 запросил параметры 3,4.
Менеджер принимает данные, отправляет их согласно списку - 1,2 первому потоку, 3,4 второму.

« Последнее редактирование: Июль 25, 2016, 17:53 от Bepec » Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #8 : Июль 25, 2016, 18:11 »

Так вот как дать первому потоку это сделать ? Как на него переключиться принудительно.
Вот поэтому и нужно вести работу с железкой в одном потоке.
В одной нитке идет взаимодействие с устройством, а из других ниток можно ставить задания (запросы) в очередь. А вот в задании можно хранить, кому нужно возвращать результат.
« Последнее редактирование: Июль 25, 2016, 18:23 от Old » Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #9 : Июль 25, 2016, 18:52 »

А я так и не распарсил.. а в чем, собственно, проблема использовать асинхронно (в фулл-дуплексе) в одном потоке?

Например:

Код
C++ (Qt)
void Foo::Foo()
{
   // шлем запросы по таймеру
   connect(timer, &QTimer::timeout, [this]() {
       const QByteArray request1 = ....;
       serial->write(request1);
   });
 
   // шлем запросы по кнопке 1
   connect(button1, &QPushButton::clicked, [this]() {
       const QByteArray request2 = ....;
       serial->write(request2);
   });
 
   // шлем запросы по кнопке N
   connect(buttonN, &QPushButton::clicked, [this]() {
       const QByteArray requestN = ....;
       serial->write(requestN2);
   });
 
   // парсим ответы
   connect(serial, &QSerialPort::readyRead, [this]() {
       // парсим входной непрерывный стрим из порта и разбираем его на ответы
       for (;;) {
           const qint64 available = serial->bytesAvailable();
           if (available < expected)
               return; // данных недостаточно, выходим до следующего readyRead.
 
           enum { StartByteIndex = ..., StopByteIndex = ..., };
           enum { StartByte = 0xAA, StopByte = 0x55, };
 
           const QByteArray peeked = serial->peek(expected); // считываем копию пакета и пытаемся его проверить
 
           // проверяем стартовый байт / поле заголовка (если есть)
           if (peeked.at(StartByteIndex) != StartByte) {
               dropBytes(1); // убираем навсегда первый символ из буфера порта, пытаемся снова синхронизироваться
               continue;
           }
 
           // проверяем стоповый байт / поле хвоста (если есть)
           if (peeked.at(StopByteIndex) != StopByte) {
               dropBytes(1); // убираем навсегда первый символ из буфера порта, пытаемся снова синхронизироваться
               continue;
           }
 
           // проверяем поле длины (если есть)
           if (peeked.at(LengthIndex) != Length) {
               dropBytes(1); // убираем навсегда первый символ из буфера порта, пытаемся снова синхронизироваться
               continue;
           }
 
           // проверяем CRC (если есть)
           if (peeked.at(CrcIndex) != CRC) {
               dropBytes(1); // убираем навсегда первый символ из буфера порта, пытаемся снова синхронизироваться
               continue;
           }
 
           // все OK, пакет годный..
 
          dropBytes(expected); // убираем весь пакет из буфера порта навсегда, он не нужен, т.к. у нас есть копия peeked
 
           // извлекаем тело с данными из peeked
 
           // чо-то с делаем с телом, обновляем UI и прочее вещи..
 
       }
 
   });
}
 
void Foo::dropBytes(qint64 bytesCount)
{
   serial->read(bytesCount); // dummy read
}
 
« Последнее редактирование: Июль 25, 2016, 18:57 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
Sphynx
Гость
« Ответ #10 : Июль 25, 2016, 19:08 »

// шлем запросы по кнопке 1
    connect(button1, &QPushButton::clicked, [this]() {
        const QByteArray request2 = ....;
        serial->write(request2);
    });
Послать то данные не проблема, а вот как вы теперь получите ответ после нажатия на кнопку ?
Записан
Sphynx
Гость
« Ответ #11 : Июль 25, 2016, 19:17 »

У меня протокол очень простой, никаких маркеров чтоб узнать на какой запрос пришел ответ нет. Только в порядке очереди.
Мне так-же неясно почему такой код зависает, и ack не становится true, если вызывать функцию по нажатию на кнопку. Просто по таймеру все работает. Что вообще происходит по команде Sleep.
Код
C++ (Qt)
while(ack==false)
   {
       Sleep(1);
   }
ack=false;
//Запускаем таймер таймаута
//Тут читаем данные с порта
while(Не все прочитали)
   {
       QApplication::processEvents();
   }
//Тут мы все прочитали или сработал таймер
ack=true;
return
 
« Последнее редактирование: Июль 25, 2016, 19:25 от Sphynx » Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #12 : Июль 25, 2016, 19:24 »

Что вообще происходит по команде Sleep.
Текущий поток засыпает.
А как может ack измениться, если пулинг последовательного порта не происходит?

Создайте очередь и работайте асинхронно в одном потоке. Отправили запрос - ждете сигнала readyRead или timeout - обрабатывайте результат - отправляйте следующий запрос из очереди - ...
Записан
Sphynx
Гость
« Ответ #13 : Июль 25, 2016, 19:39 »

Моя логика такая: если мы попали в функцию с ack=falsе, значит мы попали в нее из другого потока. Выполняя команду Sleep мы выжигаем процессорное время текущего потока, и по идее должно произойти переключение на следующий поток по очереди. И так до тех пор пока установивший ack=false поток не закончит свою работу.
Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #14 : Июль 25, 2016, 19:42 »

Моя логика такая: если мы попали в функцию с ack=falsе, значит мы попали в нее из другого потока. Выполняя команду Sleep мы выжигаем процессорное время текущего потока, и по идее должно произойти переключение на следующий поток по очереди. И так до тех пор пока установивший ack=false поток не закончит свою работу.
QSerialPort работает в том потоке, в котором был создан его объект, и "работа происходит" при работе цикла обработки событий этого потока. Если этот цикл не крутиться, то ничего происходить не будет.
Записан
Страниц: [1] 2 3 ... 5   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.116 секунд. Запросов: 22.