get (multi)return value from a std::tuple function QtConcurrentRun

Viewed 208

Hi I have a class for generate MD5 of some file in Qt, (I use tuple to return multiple value from it), I want to run it on other thread because its possible that generate all file MD5s takes some time and its freeze gui
I decide to use QtConcurrentRun for run it on other Thread, but so far I don't have any Idea about How can I get all tuple return values
here is my code

HashGen.h

#pragma once

#include "stdafx.h"

class HashGen : public QObject
{
    Q_OBJECT

public:
    HashGen(QObject *parent = nullptr);

private:
    QString Md5_gen(QString const& fname);

public slots:
    std::tuple<int, int, int> check_sequential();
};

HashGen.cpp

#include "stdafx.h"
#include "HashGen.h"

HashGen::HashGen(QObject *parent)
    : QObject(parent)
{
}

QString HashGen::Md5_gen(QString const& fname)
{
    //Generate MD5 of giving file name
}

std::tuple<int, int, int> HashGen::check_sequential() {
    QString file1[2] = { "8c0b1e6492078bdc113faae3d34fa5c5", "" }; // empty "" fill with other MD5 hash later
    QString file2[4] = { "0547f42982dd9edb7b47931d00efff15", "", "", "" };
    QString file3[2] = { "57f08e690e2655749291b2da4be8b021", "" };

    QString file1_H = Md5_gen("/proj/file.zip");
    QString file2_H = Md5_gen("/proj/file2.zip");
    QString file3_H = Md5_gen("/proj/file3.zip");

    int file1_status = 0;
    int file2_status = 0;
    int file3_status = 0;

    for (int i = 0; i < 2; i++)
    {
        if (file1[i] != "nofile")
        {
            if (file1[i] == file1_H)
            {
                file1_status = i;
                break;
            }
            else { file1_status = 422; } // Just a random number mean file doesn't match any MD5
        }
        else
        {
            file1_status = 404; // Just a random number mean file doesn't exist
            break;
        }
    }

    for (int i = 0; i < 4; i++)
    {
        if (file2[i] != "nofile")
        {
            if (file2[i] == file2_H)
            {
                file2_status = i;
                break;
            }
            else { file2_status = 422; }
        }
        else
        { 
            file2_status = 404;
            break; 
        }
    }

    for (int i = 0; i < 2; i++)
    {
        if (file3[i] != "nofile")
        {
            if (file3[i] == file3_H)
            {
                file3_status = i;
                break;
            }
            else { file3_status = 422; }
        }
        else
        {
            file3_status = 404;
            break;
        }
    }

    return { file1_status, file2_status, file3_status}; // Return all file status

mainwindow.cpp

void mainwindow::on_pushButton_clicked()
{
    QFuture<std::tuple<int, int, int>> Hash = QtConcurrent::run(Gen, &HashGen::check_sequential);
    QFutureWatcher<std::tuple<int, int, int>>* watcher = new QFutureWatcher<std::tuple<int, int, int>>;
    connect(watcher, &QFutureWatcher<std::tuple<int, int, int>>::finished, this, &MafiaDEDLFox::AfterHash);
    watcher->setFuture(Hash);
}

one other problem is I need to use QFuturewatcher to watch for QFuture, but I don't know where is best place to declare it (so its don't remove when function go out of scope) sorry if I cant explain my problem right, but I hope someone help me, thanks

1 Answers

Instead of getting value from QFutureWatcher you can send it via signal. And we can create a disposable QFutureWatcher that delete itself when the job done.

class HashGen : public QObject
{
    Q_OBJECT

public:
    HashGen(QObject *parent = nullptr);

private:
    QString Md5_gen(QString const& fname);

public slots:
    std::tuple<int, int, int> check_sequential() {
        ...

        // notify when completed
        emit check_sequential_completed(file1_status, file2_status, file3_status);
        return { file1_status, file2_status, file3_status}; // Return all file status
    }
    
    void async_check_sequential() {
        // create future watcher as child
        auto futureWatcher = new QFutureWatcher<void>(this);
        // kill yourself when you done
        connect(futureWatcher, &QFutureWatcher<void>::finished, futureWatcher, &QObject::deleteLater);
        // Wait for finish of computation when HashGen about to die
        connect(this, &QObject::destroyed, [futureWatcher](){ futureWatcher->waitForFinished(); });
        // start check sequential in another thread
        futureWatcher.setFuture(QtConcurrent::run(this, &HashGen::check_sequential));
    }

signals:
    void check_sequential_completed(int, int, int);
};

Connect check_sequential_completed to slot where you need.

connect(Gen, &HashGen::check_sequential_completed, this, &MafiaDEDLFox::AfterHash);

You don't have to make future watcher disposable of course. If you find it inefficient to create new object every time, you can keep it as member of HashGen.

Note: Instead of passing 3 int atguments, it is possible to send std::tuple<int, int, int>. You have to register meta type for queued connections which is a connection type used in interthread connections.

Q_DECLARE_METATYPE(std::tuple<int, int, int>);
qRegisterMetaType<std::tuple<int, int, int>>();

Edit1: QFutureWatcher<std::tuple<int,int,int>> replaced by QFutureWatcher<void>, no need store result in this solution.

Edit2: Connection added that waits for asynchronous computation to finish, when HashGen object destroyed. So threads won't keep running on a dead object. But it will block the main thread for a while when HashGen destroyed. Actually that's the whole point of using QFutureWatcher, otherwise if you are sure there will be no asynchronous computation when destroying HashGen object, no need to use QFutureWatcher. Just QtConcurrent::run is suffice.

Example Using Only QtConcurrent::run

Following code snipped is from an actual working project.

class AddPrinterInvoker : public QObject
{
    Q_OBJECT

public:
    AddPrinterInvoker(QObject* parent = nullptr);
    QStringList scanAddresses() {
       ...
       emit addressListReady(addressList);
       return addressList;
    }

public slots:
    void asyncScanAddresses() { QtConcurrent::run(this, &AddPrinterInvoker::scanAddresses); }

signals:
    void addressListReady(QStringList addressList);
}

Connections

connect(scanButton, SIGNAL(clicked()), m_invoker, SLOT(asyncScanAddresses()));

connect(m_invoker, SIGNAL(addressListReady(QStringList)), this, SLOT(updateAddressList(QStringList)));
Related