QtThread:I / O队列

时间:2017-08-06 20:45:30

标签: c++ multithreading qt

我试图为I / O操作创建自己的工作线程。据我所知,串口的低级管理已经在一个单独的线程中完成,但我想把我的代码放在另一个线程中来处理超时和其他中间件的东西。

这是我班级的完整代码。请注意,这是我第一次使用Qt的多线程尝试。

修改

更新代码时不会对QThread

进行子类化
#ifndef WORKERSERIAL_H
#define WORKERSERIAL_H

#include <QObject>
#include <QThread>
#include <QQueue>
#include <QMutex>
#include <QTimer>
#include <QDebug>

#include "myserial.h"

class WorkerSerial : public QObject
{
    Q_OBJECT

public:
    explicit WorkerSerial(QObject *parent = 0) : QObject(parent)
    {
        m_serial = new MySerial(this);
        connect(m_serial, &MySerial::lineReceived, this, &WorkerSerial::serial_LineReceived);
        m_stop = false;
    }

    bool open(QString port, quint32 baudrate) { return m_serial->open(port, baudrate); }
    bool isOpen() { return m_serial->serial()->isOpen(); }
    QStringList ports() { return m_serial->ports(); }

private:
    MySerial *m_serial;
    QQueue<QString> m_queue;
    QMutex m_mutex;
    bool m_stop;

private slots:
    void serial_LineReceived(QByteArray line)
    {
        emit lineReceived(line);
    }

signals:
    void lineReceived(QByteArray line);

public slots:
    void close() { m_serial->close(); }
    void send(QString data) { m_mutex.lock(); m_queue.enqueue(data); m_mutex.unlock(); }
    void stop() { m_mutex.lock(); m_stop = true; m_mutex.unlock(); }
    void run() {
        forever {
            m_mutex.lock();
            if (m_stop)
            {
                qDebug() << "Closing...";
                QTimer::singleShot(0, this, SLOT(close()));
                while(m_serial->serial()->isOpen());
                m_mutex.unlock();
                return;
            }

            if (!m_queue.isEmpty())
            {
                QString data = m_queue.dequeue();
                qDebug() << m_serial->sendMessage(data.toLocal8Bit(), true);
            }
            m_mutex.unlock();
        }
    }

};

#endif // WORKERSERIAL_H

MySerial类它是QSerialPort的便捷包装器。这里有相关的功能:

bool MySerial::open(QString port, quint32 baudrate) {
    m_serial->setPortName(port);
    if (!m_serial->open(QIODevice::ReadWrite)) return false;
    m_serial->setDataBits(QSerialPort::Data7);
    m_serial->setParity(QSerialPort::EvenParity);
    m_serial->setStopBits(QSerialPort::TwoStop);
    m_serial->setBaudRate(baudrate);
    m_serial->setFlowControl(QSerialPort::NoFlowControl);
    return true;
}

QByteArray MySerial::sendMessage(QByteArray data) {
    m_serial->write(data);
    return data;
}

最后,在这里我如何开始,使用和关闭工人:

QThread *workerThread = new QThread;
m_worker = new WorkerSerial;
m_worker->moveToThread(workerThread);
workerThread->start();
QTimer::singleShot(0, m_workerDMX, SLOT(run()));
// ...
m_worker->open("COM1", 250000));
// ...
m_worker->send("Hello World!");
// ...
m_worker->stop();
workerThread->quit();
workerThread->wait(1000);
// Here the application ends

这是输出:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QSerialPort(0x19882160), parent's thread is QThread(0x187fe598), current thread is WorkerSerial(0x19d4c9b8)
[this error appears when it calls the SendMessage() function]
"Hello World!"
Closing...
QMutex: destroying locked mutex
QThread: Destroyed while thread is still running
[the two errors above are due the singleSlot() isn't executed and thus the serial won't close]
Invalid parameter passed to C runtime function.
Invalid parameter passed to C runtime function.
很明显,我搞砸了什么! 你能帮我理解我的错误吗?

2 个答案:

答案 0 :(得分:2)

这是(不完整的)实现,它可以让您了解如何简化代码。下面还有一些注意事项:

class WorkerSerial : public QObject
{
    Q_OBJECT

public:
    explicit WorkerSerial(QObject *parent = 0) : QObject(parent)
    {
        // no need for slot -- connect directly to our signal
        connect(&m_serial, &MySerial::lineReceived, this, &WorkerSerial::lineReceived);
    }

    bool open(QString port, quint32 baudrate) { return m_serial.open(port, baudrate); }
    bool isOpen() { return m_serial.serial().isOpen(); }
    QStringList ports() { return m_serial.ports(); }

private:
    // doesn't need to be a pointer
    MySerial m_serial;

signals:
    void lineReceived(const QByteArray& line);

public slots:
    void close() { m_serial.close(); }

    void send(const QByteArray& data)
    {
        // "lengthy" function call
        qDebug() << m_serial.sendMessage(data, true);
    }
};

class Sender : public QObject
{
    Q_OBJECT

public:
    void send(const QByteArray& data) { emit sig_send(data); }

signals:
    void sig_send(const QByteArray& data);
}

...

WorkerSerial m_worker;
m_worker.open("COM1", 250000));

QThread m_thread;
m_worker.moveToThread(&m_thread);
m_thread.start();

Sender m_sender;
QObject::connect(&m_sender, &Sender::sig_send, &m_worker, &WorkerSerial::send, Qt::QueuedConnection);

m_sender.send("Hello World!");

m_thread.quit();
m_thread.wait();

m_worker.close();

附加说明:

  1. 我已经添加了一个Sender类,它在不同的线程上将调用排队到WorkerSerial::send()。如果您的代码中有信号,这会触发对WorkerSerial::send()的来电,则您不需要此课程。

  2. m_worker移动到另一个线程后, unsafe 可以访问其任何方法,除非(a)保证它们是线程安全的;或(b)使用互斥锁序列化(保护)m_serial的访问权。

答案 1 :(得分:1)

注意:这还不是一个完全可行的解决方案。但我认为最好将其作为答案发布,而不是在原始问题中添加另一段代码。

我设法采用了不同的方法。也许它看起来很奇怪......但它应该有一些优点:

  • 它保证了要传输的消息的顺序
  • 使用QQueue我可以知道队列大小或其他有关
  • 的信息
  • “变量阴影”机制应该允许与线程安全地通信

这里是代码,在最后两个问题之下:

#ifndef WORKERSERIAL_H
#define WORKERSERIAL_H

#include <QObject>
#include <QThread>
#include <QQueue>
#include <QMutex>
#include <QDebug>

#include "myserial.h"

class WorkerSerial : public QThread
{
    Q_OBJECT

public:
    explicit WorkerSerial(QObject *parent = 0, int timeout = 0) : QThread(parent), timeout(timeout)
    {
        quit = false;
        command = None;
    }

    ~WorkerSerial()
    {
        mutex.lock();
        quit = true;
        mutex.unlock();
        wait();
    }

    void open(QString port, quint32 baudrate)
    {
        mutex.lock();
        this->port = port;
        this->baudrate = baudrate;
        command = Open;
        mutex.unlock();
    }

    void close()
    {
        mutex.lock();
        command = Close;
        mutex.unlock();
    }

    void stop()
    {
        mutex.lock();
        command = Quit;
        mutex.unlock();
    }

private:
    enum Commands
    {
        None,
        Open,
        Close,
        Quit
    };

    QQueue<QString> m_queue;
    QMutex mutex;
    int timeout;
    int command;
    QString port;
    int baudrate;
    QString request;
    bool quit;

signals:
    void portStateChanged(bool isOpen);
    void lineReceived(QByteArray line);

public slots:
    void send(QString data)
    {
        mutex.lock();
        m_queue.enqueue(data);
        mutex.unlock();
    }

    void run()
    {
        MySerial serial;
        QString _port;
        int _baudrate;
        int _timeout;
        QString _request = "";
        int _command = None;

        connect(&serial, &MySerial::lineReceived, this, &WorkerSerial::lineReceived);
        mutex.lock();
        _timeout = timeout;
        mutex.unlock();

        forever {
            mutex.lock();
            _command = command;         
            command = None;
            if (!m_queue.isEmpty()) _request = m_queue.dequeue();
            mutex.unlock();

            switch (_command) {
            case Open:
                mutex.lock();
                _port = port;
                _baudrate = baudrate;
                mutex.unlock();
                if (serial.open(_port, _baudrate)) emit portStateChanged(true);
                else portStateChanged(false);
                break;

            case Close:
                serial.close();
                emit portStateChanged(serial.serial()->isOpen());
                break;

            case Quit:
                serial.close();
                return;
                break;

            default:
                break;
            }

            if (!_request.isEmpty())
            {
                qDebug() << serial.sendMessage(_request.toLocal8Bit(), true);
                _request = "";
            }
            msleep(1);
        }
    }

};

#endif // WORKERSERIAL_H

以下是如何使用它:

WorkerSerial *worker = new WorkerSerial(this);
connect(worker , &WorkerSerial::lineReceived, this, &MainWindow::lineReceived);
connect(worker , &WorkerSerial::portStateChanged, this, &MainWindow::portStateChanged);
worker->start();
// ..
worker->open("COM2", 250000);
worker->send("Hello World");
// ..
worker->stop();
worker->wait(1000);

它“有效”,但有两个主要问题:

  1. 打开了串口,但实际上并没有传输数据。 MySerial类正在运行,因为如果我执行以下操作,则绕过WorkerSerial
  2. MySerial serial;
    serial.open("COM2", 250000);
    serial.sendMessage("Hello World!", true);
    

    发送数据!因此,我的WorkerSerial仍有问题。

    1. 由于forever循环,我需要插入一个小延迟,否则CPU将最多100%运行。