我有一个Qt C ++程序。我有一个主要的驱动程序MainWindow
和一个TCPClient
类。 TCPClient
类用于与远程服务器通信,通过TCP传输一些数据,请求处理数据以及从服务器接收处理过的数据。在我的TCPClient
课程中,我使用的是QAbstractSocket
信号disconnected
。与服务器的连接断开时会发出此消息。在处理这个disconnect
信号(ifDisconnected
)的函数(槽)中,调用onCompletionCallback
的MainWindow
函数。现在我的问题是如何在所述TCPClient
完成执行后阻止执行传回onCompletionCallback
。以下是描述该问题的不完整代码;
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
没有提供任何效用函数来检查连接是否可用。
您不能也不应该阻止信号处理程序返回给调用者。否则,您将损坏您的调用堆栈。
实际问题(对我来说)是:信号处理程序的调用者是什么?
要理解我的意思,请阅读Qt doc。关于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线程本身也使用它,那么在这个被调用函数中访问的所有内容(exept局部变量)必须是互斥保护。
那么,其他选择呢:
Qt::QueuedConnection
:
当控制返回到接收者线程的事件循环时,将调用该槽。插槽在接收器的线程中执行。
对于线程之间的通信,恕我直言,Qt::QueuedConnection
是更安全的方式:TCP客户端发出一个信号,该信号导致GUI线程的事件循环中的相应条目(假设主窗口被给定为接收器对象)。 GUI线程将在处理其事件循环时获取此条目。在这种情况下,GUI线程的事件循环是信号处理程序的调用者。 TCP客户端线程在发送信号请求后没有等待,而是继续处理。如果不希望这样,第三个选项就会发挥作用:
Qt::BlockingQueuedConnection
:
与Qt :: QueuedConnection相同,除了信令线程阻塞直到槽返回。如果接收器位于信令线程中,则不得使用此连接,否则应用程序将死锁。
Qt::BlockingQueuedConnection
允许信号发射器(TCP客户端)直到GUI线程处理了信号处理程序。 (关于死锁的警告在这里无效,因为TCP客户端线程是GUI线程是接收者的信令线程。)
我有点不确定要推荐什么。我担心您的应用程序需要稍微重新设计,但为此,代码示例有点不完整。
可能的解决方案:
MainWindow::onCompletetionCallback()
将Qt::BlockingQueuedConnection
作为信号处理程序连接到此TCP客户端信号。std::atomic<bool>
(或者你留在Qt,它有自己的QAtomicInt
吊坠.TCP客户端在其主循环中或至少在发出信号后检查此标志并退出以防万一。最后一个提示:
如果你不确定你是否正确理解了所有的信号 - 我通过在信号处理程序中设置一个断点来检查我的理解,并在执行停止在该断点时检查调用堆栈。这很简单直接(除了您正在处理鼠标或拖放事件)。