0

I have a Qt C++ program. I have a main driver MainWindow and a TCPClient class. The TCPClient class is used to communicate with a remote server, transmit some data over TCP, request for processing of the data and receive processed data from server. In my TCPClient class, I am using QAbstractSocket signal disconnected. This is emitted when the connection with the server is disconnected. In the function (slot) which handles this disconnect signal (ifDisconnected), onCompletionCallback function of the MainWindow is called. Now my question is how do I prevent the transmission of execution back to TCPClient after the said onCompletionCallback finishes executing. What's following is incomplete code describing the issue;

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;
}

How do I solve this problem. I need to use the signal disconnected because QAbstractSocket doesn't provide any utility function to check if the connection is available.

Vino
  • 2,111
  • 4
  • 22
  • 42

1 Answers1

0

You cannot and you should not prevent that the signal handler returns to caller. Otherwise, you would corrupt your call stack.

The actual question (for me) is: What is the caller of signal handler?

To understand what I mean, please, read the Qt doc. about QObject::connect() with special attention to Qt::ConnectionType.

The default is Qt::AutoConnection which means:

If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.

Qt::DirectConnection:

The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.

The most common case (for me) are signal handlers called for modifications of GUI objects (i.e. widgets, etc.) which modify data or other widgets in response (in strict single-threading manner). In this case, it is Qt::DirectConnection i.e. the widget signal emitter is the caller of my signal handler.

A possible error (I did once) is to delete the widget which emitted the signal (e.g. handling a close button event of a dialog) – bad idea: I destroyed the widget with a pending method call on call stack. After returning from my signal handler it ended in a crash. The caller method (the signal emitter) had no instance anymore, or in other words: its this was invalidated. (It's like sawing the limb you sit on.) (Btw. deleteLater could be one solution for this. I found SO: How delete and deleteLater works with regards to signals and slots in Qt? concerning this.)

Considering your code sample

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

I suspect this is a Qt::DirectConnection.

The other aspect: calling a main window function out of the TCP client thread is something which needs special attention as well. The caller is something in the TCP client thread but addresses an object (the main window) which resides in the (different) GUI thread. Phew. Everything (exept local variables) what is accessed in this called function must be mutex guarded if the GUI thread itself uses this as well.

So, what about the other options:

Qt::QueuedConnection:

The slot is invoked when control returns to the event loop of the receiver's thread. The slot is executed in the receiver's thread.

For communication between threads, IMHO, the Qt::QueuedConnection is the safer way: The TCP client emits a signal which results in a respective entry in the event loop of the GUI thread (assuming the main window was given as receiver object). The GUI thread will pick up this entry while processing its event loop. In this case, the event loop of GUI thread is the caller of the signal handler. The TCP client thread didn't wait after sending the signal request but continued its processing. If this is not desired the third option comes into play:

Qt::BlockingQueuedConnection:

Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.

The Qt::BlockingQueuedConnection lets the signal emitter (the TCP client) until the GUI thread has processed the signal handler. (The warning about the dead-lock is not effective here as the TCP client thread is the signaling thread where the GUI thread is the receiver.)

I'm a little bit uncertain what to recommend. I'm afraid your application needs a little bit re-design but for this the code sample is a little bit to incomplete.

A possible solution:

  1. Introduce a Qt signal emitted when completion is required. The MainWindow::onCompletetionCallback() may be connected as signal handler to this TCP client signal using a Qt::BlockingQueuedConnection.

  2. If the end of transmission is recognized it may perhaps destroy the TCP client thread. However, a thread which kills another thread is no good idea in general (and I'm not sure how Qt handles this "under the hood"). Therefore, a better concept would be: If the end of transmission is recognized the main thread flags the TCP client thread to leave it's main loop. Flagging could be done e.g. with a std::atomic<bool> (or you stay in Qt which has its own pendant QAtomicInt. The TCP client checks this flag in its main loop or at least after emitting a signal and exits in case.

A last hint:

If you are uncertain whether you understood the all the signal stuff correctly – I checked my understanding by putting a break point into the signal handler and inspecting the call stack when execution stopped at that break point. This is easy and straight forward (except you are dealing with mouse or drag & drop events).

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56