【问题标题】:How can I prevent transmission of execution control to the last executed function when a Qt signal is emitted?发出 Qt 信号时,如何防止将执行控制传输到最后执行的函数?
【发布时间】:2017-12-30 06:31:22
【问题描述】:

我有一个 Qt C++ 程序。我有一个主驱动程序MainWindow 和一个TCPClient 类。 TCPClient 类用于与远程服务器通信,通过 TCP 传输一些数据,请求处理数据并从服务器接收处理后的数据。在我的TCPClient 课程中,我使用QAbstractSocket 信号disconnected。当与服务器的连接断开时会发出此消息。在处理这个disconnect信号(ifDisconnected)的函数(槽)中,调用MainWindowonCompletionCallback函数。现在我的问题是,在上述onCompletionCallback 完成执行后,如何防止将执行传输回TCPClient。以下是描述问题的不完整代码;

mainwindow.cpp

void MainWindow::on_connectButton_clicked()
{ 
    std::function<void(void)> callback std::bind(&MainWindow::onCompletetionCallback, this);
    tcpClient_ = new TCPClient(callback)->connectToServer(someData);
}

void MainWindow::onCompletetionCallback()
{
    if(tcpClient_->isRequestSuccess())
    {

        QJsonDocument responseJson = tcpClient_->getResponse();
        return; //When this finishes executing, I want to prevent the execution control to go back to TCPClient
    }
}

TCPClient.cpp

void TCPClient::connectToServer(QJsonDocument requestJson)
{
    // Removed code of other connect signals
    connect(tcpSocket_, &QTcpSocket::disconnected, this, &TCPClient::ifDisconnected);

}

void TCPClient::ifDisconnected()
{
    // Here the callback is called. After the callback finishes executing, I don't want execution to return to `TCPClient`.
    onCompletionCallback_();
    return;
}

我该如何解决这个问题。我需要使用信号disconnected,因为QAbstractSocket 没有提供任何实用功能来检查连接是否可用。

【问题讨论】:

    标签: c++ qt


    【解决方案1】:

    你不能也不应该阻止信号处理程序返回给调用者。否则,您会损坏您的调用堆栈。

    (对我来说)实际的问题是:信号处理程序的调用者是什么?

    要理解我的意思,请阅读 Qt 文档。关于QObject::connect(),特别关注Qt::ConnectionType

    默认是Qt::AutoConnection,这意味着:

    如果接收者位于发出信号的线程中,则使用 Qt::DirectConnection。否则,使用 Qt::QueuedConnection。连接类型在信号发出时确定。

    Qt::DirectConnection:

    当信号发出时,槽会立即被调用。该槽在信号线程中执行。

    最常见的情况(对我而言)是调用信号处理程序来修改 GUI 对象(即小部件等),它会修改数据或其他小部件作为响应(以严格的单线程方式)。在这种情况下,它是Qt::DirectConnection,即小部件信号发射器是我的信号处理程序的调用者。

    一个可能的错误(我做过一次)是删除发出信号的小部件(例如处理对话框的关闭按钮事件)——坏主意:我用调用堆栈上的挂起方法调用销毁了小部件。从我的信号处理程序返回后,它以崩溃告终。调用者方法(信号发射器)不再有实例,或者换句话说:它的this 已失效。 (这就像锯你坐在上面的肢体。)(顺便说一句。deleteLater 可能是解决此问题的一种方法。我发现 SO: How delete and deleteLater works with regards to signals and slots in Qt? 与此有关。)

    考虑您的代码示例

    connect(tcpSocket_, &QTcpSocket::disconnected, this, &TCPClient::ifDisconnected);
    

    我怀疑这是Qt::DirectConnection

    另一方面:从 TCP 客户端线程调用主窗口函数也是需要特别注意的。调用者是 TCP 客户端线程中的某物,但寻址驻留在(不同)GUI 线程中的对象(主窗口)。呸。如果 GUI 线程本身也使用它,则在此调用函数中访问的所有内容(局部变量除外)都必须受到互斥锁保护。

    那么,其他选项呢:

    Qt::QueuedConnection:

    当控制返回接收者线程的事件循环时调用该槽。该槽在接收者的线程中执行。

    对于线程之间的通信,恕我直言,Qt::QueuedConnection 是更安全的方式:TCP 客户端发出一个信号,导致 GUI 线程的事件循环中的相应条目(假设主窗口作为接收器对象给出) . GUI 线程将在处理其事件循环时获取此条目。在这种情况下,GUI 线程的事件循环是信号处理程序的调用者。 TCP 客户端线程在发送信号请求后没有等待,而是继续处理。如果这不是我们所希望的,那么第三个选项就会发挥作用:

    Qt::BlockingQueuedConnection:

    与 Qt::QueuedConnection 相同,只是信号线程阻塞直到槽返回。如果接收者位于信号线程中,则不得使用此连接,否则应用程序将死锁。

    Qt::BlockingQueuedConnection 允许信号发射器(TCP 客户端)直到 GUI 线程处理完信号处理程序。 (关于死锁的警告在这里无效,因为 TCP 客户端线程是 signaling 线程,而 GUI 线程是 receiver。)

    我有点不确定该推荐什么。恐怕您的应用程序需要重新设计,但代码示例有点不完整。

    一个可能的解决方案:

    1. 引入在需要完成时发出的 Qt 信号。 MainWindow::onCompletetionCallback() 可以作为信号处理程序使用Qt::BlockingQueuedConnection 连接到此 TCP 客户端信号。

    2. 如果识别到传输结束,它可能会破坏 TCP 客户端线程。但是,一个杀死另一个线程的线程通常不是一个好主意(我不确定 Qt 如何在“幕后”处理这个问题)。因此,更好的概念是:如果识别到传输结束,则主线程标记 TCP 客户端线程以离开其主循环。可以进行标记,例如带有std::atomic&lt;bool&gt;(或者您留在Qt中,它有自己的挂件QAtomicInt。TCP客户端在其主循环中或至少在发出信号后检查此标志并以防万一。

    最后的提示:

    如果您不确定您是否正确理解了所有信号内容 - 我通过在信号处理程序中放置一个断点并在执行在该断点处停止时检查调用堆栈来检查我的理解。这很简单直接(除非您正在处理鼠标或拖放事件)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多