c++qtsynchronizationqthreadgsoap

Synchronization between Server & Client


I Have a server which is built on gsoap. Gsoap is also written on Qt. So I can use Qt classes.

From the client I send a request to the server.

The request is the following: server supplied a phone number and message which should be sent to him. This list may be 10 000 or more, this is exactly the problem! I'm going to send a message to each number with QThread. When QThread ends its work, the history of the database should be recorded.. The problem is that we can not understand when all the history is recorded in the database.

I know this was a very bad explanation for the problem! Excuse me for my English.

Now I will try to show you the problem with the program.

I have one QThread class, which send messages. One History class, which write a history in the database.

void MainWindow::on_pushButton_clicked() //Imagine that this is a server function.
{
    QSet<int> st; //List to who sends a message.
    st << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10;
    QSet<int> clientAnswer = this->start(st);
    qDebug() << "newSet: " << clientAnswer;
    //return ClientAnswer list ot client.
}


 QSet<int> MainWindow::start(QSet<int> st)
 {
     for (auto it : st) {
         MyThread *thrd = new MyThread(it);
         History *hist = new History();
         connect(thrd, &MyThread::smsSended, hist, &History::insertHistory);
         connect(hist, &History::historyAdded, this, [=](int historyUID){
             qDebug() << "history inserted: " << historyUID;
             this->ansSet.insert(historyUID);
             if (this->ansSet.size() == st.size()) {
                 qDebug() << "mainwindow finished!";
                 emit alreadyDone();
             }
         });
         connect(thrd, &MyThread::finished, hist, &History::deleteLater);
         connect(thrd, &MyThread::finished, thrd, &MyThread::deleteLater);
         thrd->start();
     }

     return this->ansSet;
 }

MainWindow.h

private:
Ui::MainWindow *ui;
QSet<int> ansSet; //The list is to send a client.

MyThread.cpp:

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(int tInt, QObject *parent = 0);

signals:
    void smsSended(int);

public slots:
    void run();

private:
    int m_int;
};

MyThread.h

MyThread::MyThread(int tInt, QObject *parent) : QThread(parent)
{
    this->m_int = tInt;
}

void MyThread::run()
{
    qDebug() << "thread started: " << this->m_int;
    emit smsSended(this->m_int * 10);
}

History.cpp

 History::History(QObject *parent) : QObject(parent)
 {

 }

 void History::insertHistory(int insertInt)
 {
     qDebug() << "insert History: " << insertInt;
     emit historyAdded(insertInt);
 }

History.h

class History : public QObject
{
    Q_OBJECT
public:
    explicit History(QObject *parent = 0);

signals:
    void historyAdded(int hInt);

public slots:
    void insertHistory(int insertInt);
};

Application Output looks like:

thread started:  5
thread started:  1
thread started:  3
thread started:  2
thread started:  4
thread started:  7
thread started:  9
thread started:  6
newSet:  QSet()
thread started:  8
thread started:  10
insert History:  50
history inserted:  50
insert History:  10
history inserted:  10
insert History:  30
history inserted:  30
insert History:  20
history inserted:  20
insert History:  40
history inserted:  40
insert History:  70
history inserted:  70
insert History:  90
history inserted:  90
insert History:  60
history inserted:  60
insert History:  80
history inserted:  80
insert History:  100
history inserted:  100
mainwindow finished!

I know this output is right. But how I can do return a QSet ---> ansSet from MainWindow :: start function when happen if (this->ansSet.size() == st.size())? Or you have a other idea?

I'm very sorry for my English


Solution

  • First of all, lets make a primary correction to MainWindow::start so that we can reason about it:

    QSet<int> MainWindow::start(QSet<int> numbers)
    {
         History* history = new History();
    
         for (auto number : numbers) 
         {
             MyThread* sender = new MyThread(number);
             connect(sender, &MyThread::smsSent, history, &History::insertHistory);
             connect(history, &History::historyAdded, this, [=] (int historyUID) {
                 qDebug() << "History inserted: " << historyUID;
                 this->answers.insert(historyUID);
                 if (this->answers.size() == numbers.size()) {
                     qDebug() << "MainWindow finished!";
                     emit alreadyDone();
                     history->deleteLater();
                 }
             });
             connect(sender, &MyThread::finished, sender, &MyThread::deleteLater);
             sender->start();
         }
    
         return this->answers;
    }
    

    Ok, with that being done the issue is the following: you write asynchronous (non-blocking) code in one place (MainWindow::start), but in the same time trying to use it synchronously (blocking) in the other place QSet<int> clientAnswer = this->start(st);.

    Usually, if you are going async somewhere - you are going async everywhere, because async and sync do not get along together very nicely. Although, you can wait for async operation to accomplish (with Qt::BlockingQueuedConnection for instance) but why would you do so? If MainWindow::start blocks then MainWindow::on_pushButton_clicked blocks, and if MainWindow::on_pushButton_clicked blocks and MainWindow is your User Interface window, then on the button click all UI freezes, and in case when on_pushButton_clicked is a server function... your whole server becomes irresponsive.

    What I suggest you to do is the next:

    MainWindow::MainWindow() 
    {
        connect(this, &MainWindow::alreadyDone, this, &MainWindow::onAllAnswersReady); 
    }
    
    
     void MainWindow::start(QSet<int> phoneNumbers)  
     {
         History* history = new History();
         for (auto number : phoneNumbers) 
         {
             MyThread* sender = new MyThread(number);
             connect(sender, &MyThread::smsSent, history, &History::insertHistory);
             connect(history, &History::historyAdded, this, [=] (int historyUID) {
                 qDebug() << "History inserted: " << historyUID;
                 this->answers.insert(historyUID);
                 if (this->answers.size() == phoneNumbers.size()) { // all answers are ready
                     qDebug() << "MainWindow finished!";
                     emit alreadyDone();
                     history->deleteLater();
                 }
             });
             connect(sender, &MyThread::finished, sender, &MyThread::deleteLater);
             sender->start();
         }  
     }
    
    void MainWindow::on_pushButton_clicked() // Imagine that this is a server function. 
    {
        QSet<int> phoneNumbers; //List to who sends a message.
        phoneNumbers << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10;
        this->start(phoneNumbers); // <-- doesn't block 
    }
    
    void MainWindow::onAllAnswersReady() // <-- a handler function, may belong to any other class 
    {
        qDebug() << "Answers: " << this->answers;
        // Do whatever you want with the answers here.  
    }
    

    This way you do not block, but just handle all your answers as they are ready (all the history has been recorded).