从 std::tuple 函数获取(多)返回值 QtConcurrentRun

时间:2021-02-06 21:07:15

标签: c++ qt tuples

嗨,我有一个用于在 Qt 中生成某个文件的 MD5 的类(我使用元组从中返回多个值),我想在其他线程上运行它,因为生成所有文件 MD5 可能需要一些时间,并且它的冻结gui
我决定使用 QtConcurrentRun 在其他线程上运行它,但到目前为止我对如何获取所有元组返回值没有任何想法
这是我的代码

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

ma​​inwindow.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);
}

另一个问题是我需要使用 QFuturewatcher 来监视 QFuture,但我不知道在哪里声明它的最佳位置(因此当函数超出范围时它不会删除) 对不起,如果我不能正确解释我的问题,但我希望有人帮助我,谢谢

1 个答案:

答案 0 :(得分:3)

您可以通过信号发送值,而不是从 QFutureWatcher 获取值。我们可以创建一个一次性的 QFutureWatcher,它会在工作完成后自行删除。

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

check_sequential_completed 连接到您需要的插槽。

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

当然,您不必让未来的观察者成为一次性的。如果您发现每次都创建新对象效率低下,您可以将其保留为 HashGen 的成员。

注意: 可以发送 std::tuple<int, int, int>,而不是传递 3 个 int 参数。您必须为排队连接注册元类型,这是一种用于线程间连接的连接类型。

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

编辑 1: QFutureWatcher<std::tuple<int,int,int>> 替换为 QFutureWatcher<void>,无需将结果存储在此解决方案中。

编辑 2: 添加的连接等待异步计算完成,当 HashGen 对象销毁时。所以线程不会继续在死对象上运行。但是当 HashGen 销毁时它会阻塞主线程一段时间。实际上这就是使用QFutureWatcher的全部意义,否则如果您确定销毁HashGen对象时不会有异步计算,则无需使用QFutureWatcher。只需 QtConcurrent::run 就足够了。

仅使用 QtConcurrent::run 的示例

以下截取的代码来自实际工作项目。

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

连接

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

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