在处理其他信号时等待信号 [英] Wait for signal while processing other signals

查看:142
本文介绍了在处理其他信号时等待信号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的Qt应用程序谈到串行设备,偶尔不得不等待这个设备发送一个字节。要完成这个,我创建一个新的eventloop,只要有可用的信息在串行缓冲区退出:

My Qt application talks to a serial device, and occasionally has to wait for this device to send a byte. To accomplish this, I create a new eventloop that exits as soon as there is information available in the serial buffer:

unsigned char MyClass::waitForDevice(int timeout)
{
    QEventLoop wait;
    connect(d_serial, SIGNAL(readyRead()), &wait, SLOT(quit()));
    if (timeout > 0)
        QTimer::singleShot(timeout, &wait, SLOT(quit()));

    wait.exec();
    return static_cast<unsigned char>(d_serial->read(1)[0]);
}

现在的问题是,当应用程序正在等待时,正在运行,我需要能够在GUI中按下按钮时与串行设备通信。我试过将一个信号连接到一个插槽,这样做,但我发现插槽只有在 之后才执行。

Now the problem is that, while the application is waiting, i.e. while the eventloop is running, I need to be able to communicate to the serial device when a button is pressed in the GUI. Naively, I tried connecting a signal to a slot that does this, but I found that the slot is only executed after the eventloop is terminated.

我试过,没有任何运气,有一个单独的QThread运行调用 qApp-> processEvents()在无限循环,当eventloop终止时终止。这没有工作,我不太确定为什么不。什么是规范的方法来解决这个问题?

I tried, without any luck, to have a seperate QThread running that calls qApp->processEvents() in an infinite loop, which is terminated when the eventloop is terminated. This didn't work, and I'm not quite sure why not. What is the canonical way to resolve this?

推荐答案

你在前C ++ 1z世界中同步思考。在C ++ 14(和之前的)异步编程中,几乎没有任何地方存在一个 wait 的概念,它被实现为一个函数,当等待结束时返回 switch -based coroutine hacks )。你也没有使用你的应用程序是有状态的,并且状态转换可以在状态机中表示。

You're thinking synchronously in a pre-C++1z world. In C++14 (and prior) asynchronous programming, there is mostly no place for a notion of a wait that is implemented as a function that returns when the wait is over (switch-based coroutine hacks excepted). You are also not using the fact that your application is stateful, and the state transitions can be expressed in a state machine.

相反,你应该简单地对数据可用。据推测,您的应用程序可以处于多个状态。

Instead, you should simply act on data being available. Presumably, your application can be in multiple states. One of the states - the one where you have to wait for input - is simply exited when the input arrives.

下面的例子使用了一个简单的过程局部管道,但是它只是一个简单的过程局部管道。它将工作完全相同,如果你使用串行端口 - 都是 QIODevice 并发出必要的信号。我们从项目文件开始。

The example below uses a simple process-local pipe, but it would work exactly the same if you were using a serial port - both are a QIODevice and emit requisite signals. We start with the project file.

# async-comms-32309737.pro
QT       += widgets core-private
TARGET = async-comms-32309737
CONFIG   += c++11
TEMPLATE = app
SOURCES += main.cpp

为了简单,管道实现重用Qt中的 QRingBuffer 私有类。有关更多的实施,请参见此问题

To make things simple, the pipe implementation reuses the QRingBuffer private class from Qt. See this question for more fleshed-out implementation(s).

// main.cpp
#include <QtWidgets>
#include <private/qringbuffer_p.h>

/// A simple point-to-point intra-application pipe. This class is not thread-safe.
class AppPipe : public QIODevice {
   Q_OBJECT
   AppPipe * m_other { nullptr };
   QRingBuffer m_buf;
public:
   AppPipe(AppPipe * other, QObject * parent = 0) : QIODevice(parent), m_other(other) {
      open(QIODevice::ReadWrite);
   }
   void setOther(AppPipe * other) { m_other = other; }
   qint64 writeData(const char * data, qint64 maxSize) Q_DECL_OVERRIDE {
      if (!maxSize) return maxSize;
      m_other->m_buf.append(QByteArray(data, maxSize));
      emit m_other->readyRead();
      return maxSize;
   }
   qint64 readData(char * data, qint64 maxLength) Q_DECL_OVERRIDE {
      return m_buf.read(data, maxLength);
   }
   qint64 bytesAvailable() const Q_DECL_OVERRIDE {
      return m_buf.size() + QIODevice::bytesAvailable();
   }
   bool isSequential() const Q_DECL_OVERRIDE { return true; }
};

我们从一个简单的用户界面开始,一个按钮用于重新启动状态机,另一个按钮用于传输单个将由客户机接收的字节,以及指示状态机的当前状态的标签。

We start with a simple UI, with one button to restart the state machine, another to transmit a single byte that will be received by the client, and a label that indicates the current state of the state machine.

int main(int argc, char *argv[])
{
   QApplication a { argc, argv };
   QWidget ui;
   QGridLayout grid { &ui };
   QLabel state;
   QPushButton restart { "Restart" }, transmit { "Transmit" };
   grid.addWidget(&state, 0, 0, 1, 2);
   grid.addWidget(&restart, 1, 0);
   grid.addWidget(&transmit, 1, 1);
   ui.show();

现在我们创建模拟设备和客户端管道端点。

We now create the simulated device and the client pipe endpoints.

   AppPipe device { nullptr };
   AppPipe client { &device };
   device.setOther(&client);

状态机有三个状态。 s_init 是初始状态,并在1.5s延迟后退出。只有当在该状态下从设备接收到一些数据(一个字节或更多)时, s_wait 状态才会退出。在这个例子中,接收其他状态的数据没有效果。该机器设置为在停止时自动重新启动。

The state machine has three states. The s_init is the initial state, and is exited after a 1.5s delay. The s_wait state is only exited when we receive some data (a byte or more) from the device in that state. In this example, receiving the data in other states has no effect. The machine is set to restart automatically when stopped.

   QStateMachine sm;
   QState
         s_init { &sm },    // Exited after a delay
         s_wait { &sm },    // Waits for data to arrive
         s_end { &sm };     // Final state
   QTimer timer;
   timer.setSingleShot(true);

   sm.setInitialState(&s_init);
   QObject::connect(&sm, &QStateMachine::stopped, &sm, &QStateMachine::start);
   QObject::connect(&s_init, &QState::entered, [&]{ timer.start(1500); });
   s_init.addTransition(&timer, SIGNAL(timeout()), &s_wait);
   s_wait.addTransition(&client, SIGNAL(readyRead()), &s_end);

为了显示状态机的进度,我们分配 state 标签的文本属性:

To visualize the state machine's progress, we assign the state label's text property in each of the states:

   s_init.assignProperty(&state, "text", "Waiting for timeout.");
   s_wait.assignProperty(&state, "text", "Waiting for data.");
   s_end.assignProperty(&state, "text", "Done.");

最后, restart 按钮停止状态机器 - 它将自我重新启动。 transmit 按钮模拟设备发送一个字节的数据。

Finally, the restart button stops the state machine - it will self-restart then. The transmit button simulates the device sending one byte of data.

   QObject::connect(&restart, &QPushButton::clicked, &sm, &QStateMachine::stop);
   QObject::connect(&transmit, &QPushButton::clicked, [&]{
      device.write("*", 1);
   });

我们启动机器,输入事件循环,让Qt按照我们的指示从这里开始。包含 main.moc 文件,因为它包含 AppPipe 的元数据。

We start the machine, enter the event loop, and let Qt follow our directions onwards from here. The main.moc file is included for it contains the metadata for AppPipe.

   sm.start();
   return a.exec();
}

#include "main.moc"

这篇关于在处理其他信号时等待信号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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