如何在不同的线程中执行 QTcpSocket?

Posted

技术标签:

【中文标题】如何在不同的线程中执行 QTcpSocket?【英文标题】:How do I execute QTcpSocket in a different thread? 【发布时间】:2010-04-23 11:35:47 【问题描述】:

如何在不同的线程中执行 QTcpSocket 函数?

【问题讨论】:

不可能。我已经学会了这种艰难的方式。见my question。已针对这些套接字和服务器类向 Qt 提出错误建议至moveToThread () = delete。 QTBUG-82373 【参考方案1】:

QT 文档明确指出不应跨线程使用QTCPSocket。即,在主线程中创建一个QTCPSocket,并将信号绑定到另一个线程中的对象。

我怀疑您正在实现类似于 Web 服务器的东西,其中侦听在接受时创建 QTCPSocket。然后,您需要另一个线程来处理处理该套接字的任务。你不能。

我的解决方法是将套接字保留在它所在的线程中。我为该线程中的所有传入数据提供服务,并将其放入队列中,另一个线程可以处理该数据。

virtual void incomingConnection(qintptr socketDescriptor)

注意:如果在该方法的重新实现中创建了另一个套接字,则需要通过调用addPendingConnection()将其添加到Pending Connections机制中。

注意:如果您想在另一个线程中将传入连接作为新的QTcpSocket 对象处理,您必须socketDescriptor 传递给另一个线程并在那里创建QTcpSocket 对象并使用其setSocketDescriptor() 方法。

【讨论】:

它在哪里说的? 该注释是社区添加的,根据我的经验是不正确的。您似乎也无法通过传递他们声称的套接字描述符来模拟行为,因为您最终会在同一个描述符上运行两个 QTcpSocket 实例(服务器的隐式子节点和您在新线程中创建的实例),这会导致运行时断言失败。 您说您“为该线程中的所有传入数据提供服务,并将其放入另一个线程可以处理该数据的队列中。”你是怎么做到的? @NicolasHolthaus,添加了一条支持答案的注释。我还面临崩溃,我将在主线程中创建的QTcpSocket 移动到另一个工作线程。在检查堆栈跟踪后,似乎主线程仍然保留了一些 QSslSocketBackendPrivate 类型的对象,它写入了数据。这会导致崩溃。过去我误解了这种情况,并将问题归咎于我的编码错误。但似乎 Qt 库有问题。见this。将 TCP 套接字移动到另一个线程的正确方法是什么? @iammilind 最好不要移动它们,而是在不同的线程中创建它们。我添加了一个答案,显示了我用来做这件事的成语。【参考方案2】:

重要的是要注意在线程QTcpSocket 方面可以做什么和不可以做什么:

可以在非主线程中使用它,但只能在创建它的线程中使用。

不能从不同线程调用 QTcpSocket 上的不同函数,例如在一个线程中读取,在另一个线程中写入。相反,您可以为每个 QTcpSocket 创建一个单独的线程,以防止它们占用可能在主线程中绘制小部件的时间和资源。

IMO,将您的 IO(包括 QTcpSocket)放在主线程以外的线程中是最佳实践,并且对于任何高性能应用程序都必须这样做。我一直在非主线程中使用QTcpSocket,使用以下成语:


// Read data from a QTcpSocket in a thread. Assumes this is in some class.
m_thread = std::thread([this]

    QEventLoop eventLoop;
    QTcpSocket* socket = new QTcpSocket(&eventLoop);

    socket->connectToHost("localhost", 9999);

    // enqueue or process the data
    QObject::connect(socket, &QTcpSocket::readyRead, &eventLoop, [socket]
    

        m_concurrentQueue.push_back(socket->readAll());
    );

    // Quit the loop (and thread) if the socket it disconnected. You could also try
    // reconnecting
    QObject::connect(socket, &QTcpSocket::disconnected, &eventLoop, [&eventLoop]
    
        eventLoop.quit();
    );

    eventLoop.exec();

    delete socket;
);

其中m_thread 是某个成员线程(基本上只是确保它的生命周期大于当前的直接作用域),而m_concurrentQueue 是某个线程安全队列,或者std 具有互斥保护的容器。

您还需要将一些信号(我通常称之为joinAll)连接到事件循环退出函数,并从类析构函数中调用它。当使用线程中的事件循环习语时,您始终必须小心确保您可以正确地销毁类,否则您的程序将不会退出(或者在 Windows 上它将被终止,通常使用一些析构函数没有被调用,它最终成为一个静默错误)。

我通常也使用条件变量在创建线程后等待,直到事件循环开始。这不是必需的,但如果您将这些线程放在构造函数中,它可以帮助使程序流程更有意义。

【讨论】:

感谢您的回答。我已经在你发帖的那天投了赞成票。我发现QTcpSocketQWebSocket 不是线程可移动的这一事实很难!在这方面向 Qt 提出了一个错误,并将其放在上述问题之下。 @Nicolas,这个解决方案看起来不错,但我想知道如何在主线程中处理m_concurrentQueue 的数据,我的意思是我应该使用QTimer 还是只是通知主线程开始处理队列数据的信号?哪个变体在性能方面最好?谢谢。 是否有理由为 eventLoop.quit 使用 lambda 而不是 &QEventLoop::quit @cdgraham 我不这么认为,可能是从在退出时进行了一些清理的东西中复制的。【参考方案3】:

QMutex 锁定所有调用,不仅在“不同”线程上,而且在所有线程上。一种简单的方法是通过QMutexLocker

【讨论】:

"Note: All functions in this class are reentrant"。如果您的矛盾主张有更权威的来源,请附上。 你是对的。但是,(我的意思是)该类不是线程安全的,并且重入仅保证您可以在具有不同实例的情况下从多个线程使用该类,但不同的QTcpSocket 实例不能共享套接字描述符.此外,QTcpSocket 的单个实例不能被多个线程访问,无论它是否是互斥的。【参考方案4】:

我在文档中读到的是 QTcpSocket 不应跨线程使用。如果你想在另一个线程中使用它 Qt 文档说你应该在你的线程中创建一个新的 QTcpSocket 实例并在新实例上设置描述符。为此,您需要重新实现 QTcpServer 并使用QTcpServer::incomingConnection。提供了一个简单的例子here。

【讨论】:

以上是关于如何在不同的线程中执行 QTcpSocket?的主要内容,如果未能解决你的问题,请参考以下文章

Qt笔记-QTcpSocket跨线程调用(官方推荐方法,非百度烂大街方法)

如何将用户输入从控制台传递到不同线程中的对象-> QTcpsocket

Qt中的全局变量声明

如何安全地删除 QT::QTcpSocket?

QTcpSocket 在工作进程中连续写入。避免内存泄漏的最佳实践

启动连接后将 QTcpSocket 移动到新线程