简体   繁体   English

为什么 QDialog::exec() 会阻止 QTcpSocket 工作

[英]Why does QDialog::exec() blocks QTcpSocket work

I make a Bomberman (or DynaBlast) game clone with multiplayer.我用多人游戏制作了一个 Bomberman(或 DynaBlast)游戏克隆。 Server game and client game communicate through messages using QTcpSocket.服务器游戏和客户端游戏使用 QTcpSocket 通过消息进行通信。 Typical workflow for playing network game is following:玩网络游戏的典型工作流程如下:

  • Server player execs NetworkGame dialog, among other things this dialog creates NetworkGame object服务器播放器执行 NetworkGame 对话框,除此之外,此对话框创建 NetworkGame object
  • Client player execs ClientGame dialog, among other things this dialog creates ClientGame object客户端玩家执行 ClientGame 对话框,除此之外,此对话框创建 ClientGame object
  • Client chooses IP of server and clicks "Connect"客户端选择服务器的 IP 并点击“连接”
  • Server accepts connections, server and clients are now able to send messages each other服务器接受连接,服务器和客户端现在能够互相发送消息
  • Client sends "ready"-message, server start game客户端发送“就绪”消息,服务器开始游戏
  • When game over, the game object emits GameStatusChanged(GameStatus newStatus) signal.游戏结束时,游戏 object 发出GameStatusChanged(GameStatus newStatus)信号。 This signal connected to MainWindow, which execs GameOverDialog .此信号连接到执行GameOverDialog的 MainWindow。 If player chooses "Play again" at GameOverDialog , MainWindow execs NetworkGame or ClientGame dialogs again and we are at the first points.如果玩家在GameOverDialog选择“再次播放”,MainWindow 将再次执行 NetworkGame 或 ClientGame 对话框,我们就在第一点。

So, after first game is over, second exec of ClientGameDialog blocks QTcpSocket work in the way it cann't read data or emit QTcpSocket::readyRead signal (I don't know which one point exactly).因此,在第一场比赛结束后, ClientGameDialog的第二个exec以无法读取数据或发出QTcpSocket::readyRead信号的方式阻止QTcpSocket工作(我不知道究竟是哪一点)。 ClientGameDialog 's GUI is responsive, it can send messages to server, but it cann't read messages. ClientGameDialog的 GUI 是响应式的,它可以向服务器发送消息,但不能读取消息。 At the same time NetworkGame and NetworkGameDialog work properly - they are able to send and receive messages.同时NetworkGameNetworkGameDialog正常工作——它们能够发送和接收消息。 I checked all my classes several times and don't see any significant difference.我检查了我所有的课程几次,没有发现任何显着差异。

I think full code is to huge to post it here, so I gave UML a try.我认为完整的代码在这里发布它是巨大的,所以我尝试了 UML。 This is a chart for most important classes.这是最重要的类的图表。 Green arrow designates Qt's child-parent relations, starting at a child QObject it points to a parent.绿色箭头表示 Qt 的子父关系,从指向父对象的子 QObject 开始。 在此处输入图像描述

When Socket class receives new message through QTcpSocket interface, it emits messageReceived(const Message& message) signal;Socket class 通过QTcpSocket接口接收到新消息时,发出messageReceived(const Message& message)信号; other classes can connect to this signal via slots and handle messages.其他类可以通过槽连接到这个信号并处理消息。 I don't see what Client , ServerWorker , Server classes can do with event loop, the just help to process raw data from QTcpSocket and deliver messages to other classes, particulary to Game and Dialog classes.我看不到ClientServerWorkerServer类可以用事件循环做什么,只是帮助处理来自QTcpSocket的原始数据并将消息传递给其他类,特别是GameDialog类。

Here is some code (I have some code duplications, I leave them until better times).这是一些代码(我有一些代码重复,我把它们留到更好的时候)。 Creating game:创建游戏:

// Server game
void MainWindow::startNetworkGame() // User clicked "Start network game" button
{
    const auto& player = mainMenuWidget_->selectedPlayer();
    gameDialogs_ = createGameDialogs(this, GameType::Server, player);
    auto answer  = gameDialogs_.creationDialog->exec();
    if (answer == QDialog::Accepted) {
        auto initializationData = gameDialogs_.creationDialog->initializationData();
        initializeGame(initializationData);
        startGame(initializationData);
    }
}

// Client game
void MainWindow::connectToServer() // User clicked "Connect to server" button
{
    const auto& player = mainMenuWidget_->selectedPlayer();
    gameDialogs_       = createGameDialogs(this, GameType::Client, player);
    auto answer        = gameDialogs_.creationDialog->exec(); // At first time it works fine
    if (answer == QDialog::Accepted) {
        auto initializationData = gameDialogs_.creationDialog->initializationData();
        initializeGame(initializationData);
        startGame(initializationData);
    }
}

Next snippet is code for processing GameStatusChanged signal when game was over:下一个片段是游戏结束时处理GameStatusChanged信号的代码:

void MainWindow::gameStatusChanged(GameStatus newStatus)
{
    if (newStatus == GameStatus::GameOver) {
        auto* gameOverDialog = gameDialogs_.gameOverDialog;
        gameOverDialog->setGameResult(gameData_.game->gameResult());
        auto gameOverDialogAnswer = gameOverDialog->exec();
        if (gameOverDialogAnswer == QDialog::Accepted) {
            gameDialogs_.creationDialog->reset();
            auto answer = gameDialogs_.creationDialog->exec(); // At this point client cann't receive messages, but server can.
            if (answer == QDialog::Accepted) {
                auto initializationData = gameDialogs_.creationDialog->initializationData();
                initializeGame(initializationData);
                startGame(initializationData);
            } else {
                showMainMenu();
            }
        } else {
            showMainMenu();
        }
    }
}

I suspect that ClientGameDialog 's event loop (is it indeed has it's own event loop) doesn't processes QTcpSocket 's events.我怀疑ClientGameDialog的事件循环(它确实有自己的事件循环)不处理QTcpSocket的事件。 I tried to replace exec() with open methods for client dialog:我尝试将exec()替换为客户端对话框的open方法:

void MainWindow::gameStatusChanged(GameStatus newStatus)
{
    if (newStatus == GameStatus::GameOver) {
        auto* gameOverDialog = gameDialogs_.gameOverDialog;
        gameOverDialog->setGameResult(gameData_.game->gameResult());
        auto gameOverDialogAnswer = gameOverDialog->exec();
        if (gameOverDialogAnswer == QDialog::Accepted) {
            gameDialogs_.creationDialog->reset();
            auto d = qobject_cast<ClientGameDialog*>(gameDialogs_.creationDialog);
            if (d) {
                gameDialogs_.creationDialog->open();
            } else {
                auto answer = gameDialogs_.creationDialog->exec();
                if (answer == QDialog::Accepted) {
                    auto initializationData = gameDialogs_.creationDialog->initializationData();
                    initializeGame(initializationData);
                    startGame(initializationData);
                } else {
                    showMainMenu();
                }
            }
        } else {
            showMainMenu();
        }
    }
}

It works, but I want to find where was the problem.它有效,但我想找出问题所在。 Maybe someone can prompt, where to search a solution.也许有人可以提示,在哪里搜索解决方案。 Main questions are: where the difference between Server and Client code flows and why Client code works fine at the first time and breaks at the second.主要问题是:服务器和客户端代码之间的差异在哪里流动以及为什么客户端代码在第一次运行良好而在第二次中断。

This is because QDialog::exec is a synchronous blocking operation that will stop the event loop of the main thread and start a new event loop for this dialog.这是因为QDialog::exec是一个同步阻塞操作,它将停止主线程的事件循环并为此对话框启动一个新的事件循环。 This most of the time is not a problem unless you are doing some continuous work on the main thread such as processing QTCPSocket这大部分时间都不是问题,除非你在主线程上做一些连续的工作,比如处理 QTCPSocket

Instead of using QDialog::exec use QDialog::open which is asynchronous and does not start a new event loop, you can simply connect signals from the dialog to read the results once the user will accept/close the dialog.而不是使用 QDialog::exec 使用QDialog::open ,它是异步的并且不会启动新的事件循环,您可以简单地连接来自对话框的信号以在用户接受/关闭对话框后读取结果。

If you require blocking dialogs then you also can simply offload QTcpSocket to another thread and do whole processing asynchronously and only emit required updates to the main GUI thread.如果您需要阻塞对话框,那么您也可以简单地将 QTcpSocket 卸载到另一个线程并异步执行整个处理,并且只向主 GUI 线程发出所需的更新。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM