прием udp в потоке
От: 00011011  
Дата: 15.11.21 13:36
Оценка:
Сделал прием udp в отдельном потоке. Но стабильно ловится только первый пакет.
Дальше начинается странное — иногда, если походить в отладке, вновь приходим в слот приема. Если без отладки, то не приходит больше одного раза.
Ранее реализованный прием без потока работает идеально. В программе отправки уверен, она написана давно и не на Qt, многократно проверена, умеет отправлять и принимать по udp пакеты. В том числе сама себе через локалхост.
class Receiver : public QThread
{
    Q_OBJECT

public:
    Receiver(QObject *parent);
    ~Receiver();
    unsigned long m_Size;
protected:
    virtual void run();
public slots:
    void onReceive();
private:
    QUdpSocket *m_pSocket;
};

Receiver::Receiver(QObject *parent)
    : QThread(parent)
{
    m_pSocket = nullptr;
    m_Size = 0;
}

Receiver::~Receiver()
{
    quit();
    wait();
}

void Receiver::run()
{
    m_pSocket = new QUdpSocket;
    m_pSocket->bind(QHostAddress::Any, 7000);
    connect(m_pSocket, &QUdpSocket::readyRead, this, &Receiver::onReceive);
    
    exec();

    disconnect(m_pSocket, &QUdpSocket::readyRead, this, &Receiver::onReceive);
    delete m_pSocket;
    m_pSocket = nullptr;
}

void Receiver::onReceive()
{
    while (m_pSocket->hasPendingDatagrams()) {
        QByteArray data;
        int size = m_pSocket->pendingDatagramSize();
        data.resize(size);
        m_pSocket->readDatagram(data.data(), size);
        m_Size += size;
    }
}

запуск всего этого снаружи, из главного (gui) потока. m_Receiver это переменная класса главного окна.
m_Receiver.start();
Отредактировано 15.11.2021 13:38 00011011 . Предыдущая версия .
Re: прием udp в потоке
От: Zhendos  
Дата: 15.11.21 15:33
Оценка:
Здравствуйте, 00011011, Вы писали:

0>Сделал прием udp в отдельном потоке. Но стабильно ловится только первый пакет.

0>Дальше начинается странное — иногда, если походить в отладке, вновь приходим в слот приема. Если без отладки, то не приходит больше одного раза.

1. Вы используете "this" в connect. QThread тоже QObject,
и он имеет связанный с ним поток и этот поток это не он сам. А вот
"socket" создан в самом потоке, в результате не только сигналы
доставляются через очередь сообщения, а не прямым вызовом,
но и есть явная "data-race". Так как "onReceive" вызывается в другом потоке,
а обращается к сокету из потока Receiver.
Вообще наследование от QThread это плохой подход в современном Qt.
Все реализовано так, что можно обойтись без этого.

2. Нужно к другим сигналам подключиться, по крайней мере к тем что сообщают об изменении
состояния и ошибках: errorOccurred/stateChanged

3. Зачем вручную выделять память?
Re[2]: прием udp в потоке
От: 00011011  
Дата: 15.11.21 17:24
Оценка:
Здравствуйте, Zhendos, Вы писали:

Z> Вообще наследование от QThread это плохой подход в современном Qt.

Z> Все реализовано так, что можно обойтись без этого.

Подскажите какие способы есть хорошие. Отдельные потоки мне нужны только из-за требований к высокой часоте обмена, которая скороее всего подвесит GUI, если делать все в одном потоке.
Re[3]: прием udp в потоке
От: Zhendos  
Дата: 15.11.21 18:00
Оценка: +1
Здравствуйте, 00011011, Вы писали:

0>Здравствуйте, Zhendos, Вы писали:


Z>> Вообще наследование от QThread это плохой подход в современном Qt.

Z>> Все реализовано так, что можно обойтись без этого.

0>Подскажите какие способы есть хорошие. Отдельные потоки мне нужны только из-за требований к высокой часоте обмена, которая скороее всего подвесит GUI, если делать все в одном потоке.


Ну в потоке QThread уже все есть для того чтобы от него не наследовать, а использовать как есть,
это раньше метод run был чисто абстрактный, теперь он вызывает exec.

Теперь есть сигналы finished где можно освобождать
объекты (можно просто связать с QObject::deleteLater),
сигнал started где можно инициализировать в контексте потока.

Основная идея, которую продвигают разработчики Qt, что QThread это класс
для управления потоками, а не класс для асинхронной работы.
Нужно что-то делать в другом потоке, унаследуйте от QObject,
вызовете moveToThread, свяжите сигналы и слоты QThread с этим QObject и вперед.

Вот статья от разработчиков Qt по этому поводу: https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong .
Re[4]: прием udp в потоке
От: 00011011  
Дата: 15.11.21 19:14
Оценка:
Здравствуйте, Zhendos, Вы писали:

Z>Вот статья от разработчиков Qt по этому поводу: https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong .


Низкоуровневые API для многопоточности предполагают наличие некоторой функции создания потока, которой передается в качестве аргумента указатель на гдавную функцию потока и ее аргументы (CreateThread, pthread_create и т.д). Нечто похожее есть в QThread: static QThread *create(Function &&f, Args &&... args). Предлагаете использовать ее? Есть какие-то примеры как надо делать?

Я использовал в качестве образца официальный пример corelib/thread/mandelbrot. Там как раз через наследование от QThread. но мне наследование как таковое не нужно (как и сигналы/слоты), мне нужно просто запустить поток, не заморачиваясь с "принадлежностью" объектов к потоку, и наиболее оптимально принимать данные по udp. Поток запускается при старте программы и закрывается при ее завершении (да, нужно чтобы он не зависал при завершении, т.е. все-же какойто механизм корректного завершения).

Честно говоря, для меня оказалось удивительно что объекты Qt каким-то образом принадлежат потокам, а не просто существуют в памяти. В чем физический смысл этой принадлежности? Мне кажется, это какое-то усложненение, наверное обусловленное архитектурой Qt. Хотя я об этом где-то прочитал, и по этой причине объявил сокет изначально как локальную переменную в функции потока, а затем оказалось что он нужен и в слоте — поэтому по быстрому переделал в указатель.
Re: прием udp в потоке
От: Igore Россия  
Дата: 16.11.21 07:07
Оценка: 2 (1)
Здравствуйте, 00011011, Вы писали:

0>Сделал прием udp в отдельном потоке. Но стабильно ловится только первый пакет.

0>Дальше начинается странное — иногда, если походить в отладке, вновь приходим в слот приема. Если без отладки, то не приходит больше одного раза.
0>Ранее реализованный прием без потока работает идеально. В программе отправки уверен, она написана давно и не на Qt, многократно проверена, умеет отправлять и принимать по udp пакеты. В том числе сама себе через локалхост.
class Receiver : public QObject
{
    Q_OBJECT

public:
    Receiver(QObject *parent);
    ~Receiver();

    unsigned long m_Size = 0;
    void start();
public slots:
    void doStart();
    void onReceive();
private:
    QScopedPointer<QUdpSocket> m_pSocket;
};

Receiver::Receiver(QObject *parent)
    : QObject(parent)
{
}

Receiver::~Receiver()
{
}

Receiver::start()
{
    QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection);
}

Receiver::stop()
{
    QMetaObject::invokeMethod(this, "doStop", Qt::QueuedConnection);
}


void Receiver::doStart()
{
    m_pSocket.reset( new QUdpSocket() );
    m_pSocket->bind(QHostAddress::Any, 7000);
    connect(m_pSocket.data(), &QUdpSocket::readyRead, this, &Receiver::onReceive);
}

void Receiver::doStop()
{
    m_pSocker.reset();
}

void Receiver::onReceive()
{
    while (m_pSocket->hasPendingDatagrams()) {
        QByteArray data;
        int size = m_pSocket->pendingDatagramSize();
        data.resize(size);
        m_pSocket->readDatagram(data.data(), size);
        m_Size += size;
    }
}

0>запуск всего этого снаружи, из главного (gui) потока. m_Receiver это переменная класса главного окна.
Receiver m_Receiver;
QThread m_Thread;

m_Receiver.moveToThread( &m_Thread );
m_Receiver.start();
m_Thread.start();

m_Receiver.stop();
m_Thread.quit();
m_Thread.waitForFinished();

А если так попробовать?
Отредактировано 16.11.2021 7:08 Igore . Предыдущая версия .
Re[5]: прием udp в потоке
От: Zhendos  
Дата: 16.11.21 10:43
Оценка: 4 (1) +1
Здравствуйте, 00011011, Вы писали:

0>Здравствуйте, Zhendos, Вы писали:


Z>>Вот статья от разработчиков Qt по этому поводу: https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong .


0>Низкоуровневые API для многопоточности предполагают наличие некоторой функции создания потока, которой передается в качестве аргумента указатель на гдавную функцию потока и ее аргументы (CreateThread, pthread_create и т.д). Нечто похожее есть в QThread: static QThread *create(Function &&f, Args &&... args). Предлагаете использовать ее? Есть какие-то примеры как надо делать?


Ну все зависит от ваших задач. Я предлагаю только не использовать наследование от QThread.
Пример как это делать самый первый в документации к QThread. Ну и здесь уже в посте
есть другой пример, но с тем же принципом — наследование от QObject.

0>Честно говоря, для меня оказалось удивительно что объекты Qt каким-то образом принадлежат потокам, а не просто существуют в памяти. В чем физический смысл этой принадлежности? Мне кажется, это какое-то усложненение, наверное обусловленное архитектурой Qt. Хотя я об этом где-то прочитал, и по этой причине объявил сокет изначально как локальную переменную в функции потока, а затем оказалось что он нужен и в слоте — поэтому по быстрому переделал в указатель.


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

Пользователь обычно не делает слоты рассчитанными на вызов из разных потоков
одновременно. Поэтому `connect` заботиться об этом, используя информацию о
том что "receiver" и "sender" располагаются в разных потоках, `connect` заботиться
о том, чтобы несмотря на то что сигнал был вызван в потоке в котором живет
"sender", слот будет вызван в потоке "receiver". И никакой "data-race" не будет.
Так же это помогает в отладочной сборке валидировать с помощью assert и "QThread::currentThread"
правильное использование потоков.

И в эту прекрасную картину правильного использования многопоточности не укладывается только
одна вещь — QThread. Вернее его использования с наследованием, интуитивно кажется что
сам QThread должен принадлежать самому себе. Но это не так, по умолчанию
он принадлежит потоку в котором создан.
Отредактировано 28.12.2021 20:21 Zhendos . Предыдущая версия .
Re[2]: прием udp в потоке
От: 00011011  
Дата: 16.11.21 12:03
Оценка:
Здравствуйте, Igore, Вы писали:

I>А если так попробовать?


да, так работает, спасибо!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.