Qt应用程序中具有共享资源的工作线程 [英] Worker threads with shared resources in Qt application

查看:85
本文介绍了Qt应用程序中具有共享资源的工作线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究一个Qt应用程序,该应用程序涉及与一个或多个设备的串行通信.可以同时执行不同的过程,并且每个过程可以向设备发送一个或未知数量的命令,并且可以作为响应接收数据.为了更加清楚,这是该方案的图形化图示:

I am working on a Qt application which involves serial communication with one or multiple devices. There are different procedures that can be executed simulteanously and each procedure may send one or unknown number of commands to a device and may receive data in response. To make it more clear, here is a graphical illustration of the scenario:

单击按钮将触发相应过程的执行. 因此,当用户在短时间内单击两个或多个按钮时,可能会同时运行两个或多个不同的过程.实际上,它们之间唯一可以共享的是与单个设备的串行通信. 这是过程的外观的两个伪代码示例:

Clicking on a button triggers the execution of the corresponding procedure. So two or more different procedures may be running at the same time when the user clicks on two or more buttons in a short interval. Actually the only thing that may be shared between them is the serial communication with a single device; otherwise they are mostly independent of one another. And here are two pseudo-code examples of what a procedure may look like:

过程A:

begin
write command a1 on serial port
wait for one second
perform some computations
write command a2 on serial port
wait for one second
end

程序B:

begin
while true:
    write command b1 on serial port
    read the response from serial port
    perform some computations
    if a condition holds return, otherwise continue
end

我的解决方案及其问题:

为简化这种情况,请考虑只有一台设备需要与之通信.由于过程可以同时执行(并且一次只能有一个可以通过串行端口与设备通信),因此我为每个过程创建了一个线程和一个工作程序类,并将工作程序移到了相应的线程上.为了在访问串行端口时同步过程,我创建了一个互斥锁:

My solution and its issue:

To simplify the situation consider that there is only one device which we need to communicate with. Since procedures can be executed simulteanously (and only one of them can communicate with the device through serial port at a time) I have created one thread and one worker class for each of the procedures and have moved the workers to their corresponding threads. To synchronize procedures when accessing the serial port I have created one mutex:

MainWindow.h

class MainWindow : public QMainWindow {

public:
    //...
    QSerialPort*    serial_;
    QMutex      serial_mutex_;

private:
    //...
    ProcAWorker*    proca_worker;
    ProcBWorker*    procb_worker;
    ProcCWorker*    procc_worker;
    ProcDWorker*    procd_worker;

    QThread     proca_thread;
    QThread     procb_thread;
    QThread     procc_thread;
    QThread     procd_thread;

}

MainWindow.cpp

void MainWindow::onConnectButtonClicked()
{
    serial_ = new QSerialPort();
    // configure serial port settings

    serial_->open(QIODevice::ReadWrite);
}

void MainWindow::onButtonAClicked()
{
    proca_worker = new ProcAWorker(0, this);   // pass a pointer to this class to be able to access its methods and members
    proca_worker->moveToThread(&proca_thread);

    // setup worker-thread connections: started, quit, finished, etc.

    proca_thread.start();    // triggers `proccess` slot in proca_worker
}

// same thing for other buttons and procedures

ProcAWorker.cpp

void ProcAWorker::ProcAWorker(QObject *parent, QMainWindow *wnd) :
    QObject(parent), wnd_(wnd)
{

}

void ProcAWorker::process()
{
    wnd_->serial_mutex_->lock();
    wnd_->serial_->write('Command a1');   // Warning occurs in this line
    bool write_ok = client_->serial_->waitForBytesWritten(SERIAL_WRITE_TIMEOUT);
    wnd_->serial_mutex_->unlock();

    QThread::sleep(1);
    // perform some computations

    wnd_->serial_mutex_->lock();
    wnd_->serial_->write('Command a2');
    bool write_ok = client_->serial_->waitForBytesWritten(SERIAL_WRITE_TIMEOUT);
    wnd_->serial_mutex_->unlock();

    if (write_ok) {
        // signal successful to main window
        emit success();
    }
}

但是,在串行端口(即wnd_->serial_->write('Command a1');)上执行写操作时,会显示以下警告:

However, when the write operation is performed on the serial port (i.e. wnd_->serial_->write('Command a1');) the following warning is shown:

QObject:无法为处于不同状态的父级创建子级 线. (父母是QSerialPort(0x18907d0),父母的线程是 QThread(0x13cbc50),当前线程为QThread(0x17d8d08)

QObject: Cannot create children for a parent that is in a different thread. (Parent is QSerialPort(0x18907d0), parent's thread is QThread(0x13cbc50), current thread is QThread(0x17d8d08)

我的问题:

1)我已经查看了关于此警告的有关Stackoverflow的其他问题,但他们的回答仅提到应使用信号/插槽.我熟悉使用信号/插槽与工作线程进行通信.但是,我无法弄清楚如何使用信号/插槽实现我的特定方案(具有共享资源(如串行端口)的同时运行过程)或如何修改当前解决方案来解决此问题? 请注意,应允许这些过程并行运行(除非它们想与设备进行通信的那一刻).显然,一个人可以按顺序运行这些程序(即一个接一个地运行),但我不是在寻找这种解决方案.

1) I have already looked at other questions on Stackoverflow regarding this warning, but their answers have only mentioned that signal/slot should be used. I am familiar with using signal/slot to communicate with worker threads. However, I can't figure out how to implement my specific scenario (simultaneous running procedures with shared resources like serial port) using signal/slot or how can I modify my current solution to resolve this issue? Note that the procedures should be allowed to run in parallel (unless in those moments when they want to communicate with the device). Obviously one can run the procedures sequentially (i.e. one after another) but I am not looking for such solutions.

2)实际上,还有一个暂停"按钮可以停止所有正在运行的过程,并向设备发送暂停命令.但是我也想不出要实现此功能(设置标志,发送退出信号等).您能否在这方面也给我一些提示?

2) Actually there is also a "Halt" button that stops all the running procedures and sends a halt command to the device. But I could not figure out to implement this functionality as well (set a flag, send a quit signal, etc.). Could you please give me some hints in this regards as well?

推荐答案

首先,您不需要显式多线程(它是可选的),其次,您不需要任何手动管理的同步原语.

First of all, you don't need explicit multithreading (it's optional), second of all you don't need any manually managed synchronization primitives.

然后,使用状态机为每个过程建模.希望通信协议允许每个过程识别对其自己命令的响应,这样,即使您将传入的数据复制到所有过程中,它们也将忽略与它们无关的数据.

Then, model each procedure using a state machine. Hopefully the communication protocol allows each procedure recognize the responses to its own commands, so that even though you'd be replicating the incoming data to all of the procedures, they'd ignore the data irrelevant to them.

此答案给出了一种解决方案的示意图,该解决方案可以完全满足您的需求,无需多路复用.通过本地管道公开QIODevice时,它是微不足道的:从端口传入的所有内容都被写入到端口的一端.一或多个本地管道.从管道传入的所有内容都将写入端口.只要您在Unbuffered模式下打开它们的过程,管道就将保持数据包的完整性.这样,每个write都将作为连续的字节块到达串行端口,并以相同的方式写入端口.

This answer has a sketch of a solution that does exactly what you want, sans multiplexing. Multiplexing a QIODevice is trivial when you expose it via local pipes: everything incoming from the port is written to one end of one or more local pipes. Everything incoming from the pipes is written to the port. The pipes will maintain the integrity of the packets as long as you open their procedure end in Unbuffered mode. That way each write will arrive at the serial port as a contiguous block of bytes, and will be written to the port in the same manner.

您将如何复用?像这样:

How would you multiplex? Like so:

class IODeviceMux : public QObject {
  Q_OBJECT
  QVector<QPointer<AppPipe>> m_portPipes;
  QVector<QPointer<AppPipe>> m_userPipes;
  QPointer<QSerialPort> m_port;
public:
  IODeviceMux(QObject *parent = {}) : QObject(parent) {}
  void setPort(QIODevice *port) {
    if (m_port) {
      disconnect(m_port.get(), 0, this, 0);
      m_userPipes.removeAll({});
      for (auto pipe : qAsConst(m_userPipes))
        disconnect(m_port.get(), 0, pipe.get(), 0);
    }
    m_port = port;
    connect(m_port.get(), &QIODevice::readyRead, this, &IODeviceMux::onPortRead);
  }
  AppPipe *getPipe() {
    QScopedPointer<AppPipe> user(new AppPipe(QIODevice::ReadWrite | QIODevice::Unbuffered));
    auto *port = new AppPipe(QIODevice::ReadWrite | QIODevice::Unbuffered, this);
    user->addOther(port);
    connect(port, &QIODevice::readyRead, this, &IODeviceMux::onPipeRead);
    connect(m_port.get(), &QIODevice::bytesWritten, user.get(), &QIODevice::bytesWritten);
    connect(user, &QObject::destroyed, port, &QObject::deleteLater);
    m_userPipes.push_back(user.get());
    m_portPipes.push_back(port);
    return user.take();
  } 
private:
  void onPortRead() {
    if (!m_port) return;
    auto data = m_port->readAll();
    m_portPipes.removeAll({});
    for (auto pipe : qAsConst(m_portPipes))
      pipe->write(data);
  }
  void onPipeRead() {
    auto *pipe = qobject_cast<AppPipe*>(sender());
    QByteArray data;
    if (pipe) data = pipe->readAll();
    if (m_port) m_port->write(data);
  }
};

每个过程都将getPipe()对待管道,就好像它是一个串行端口设备一样.管道中的每个write都会在端口上忠实执行.端口上的每个readyRead都将忠实地转发,并且立即可以读取相同的数据量.甚至端口的bytesWritten也被转发.但是bytesToWrite不起作用-它总是返回零.可以通过在AppPipe中添加一个选项来查询此值来解决此问题.

The procedures would each getPipe() and treat the pipe as if it was a serial port device. Each write into a pipe gets faithfully executed on the port. Each readyRead on the port is faithfully forwarded, with same data amounts available immediately to read. Even the port's bytesWritten is forwarded. But bytesToWrite doesn't work - it always returns zero. This could be fixed by adding an option to AppPipe to query this value.

我想,这就是使它正常工作所需的一切.

That's about all you need to get it to work, I'd think.

这篇关于Qt应用程序中具有共享资源的工作线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆