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

Posted

技术标签:

【中文标题】从 std::tuple 函数 QtConcurrentRun 获取(多)返回值【英文标题】:get (multi)return value from a std::tuple function QtConcurrentRun 【发布时间】:2021-02-06 21:07:15 【问题描述】:

嗨,我有一个类用于在 Qt 中生成某个文件的 MD5,(我使用元组从中返回多个值),我想在其他线程上运行它,因为生成所有文件 MD5 可能需要一些时间,而且它冻结图形界面 我决定使用 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】:

您可以通过信号发送它,而不是从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&lt;int, int, int&gt;,而不是传递 3 个 int 参数。您必须为队列连接注册元类型,这是一种用于线程间连接的连接类型。

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

编辑1: QFutureWatcher&lt;std::tuple&lt;int,int,int&gt;&gt; 替换为QFutureWatcher&lt;void&gt;,此解决方案无需存储结果。

编辑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)));

【讨论】:

感谢您的出色回答,所以您说不需要使用元组函数返回多个值,而是使用信号并将所有输出发送到 GUI 线程中的插槽更好?只有一个问题,所以你认为不需要QFutureQFutureWatcher,只需使用QtConcurrent 运行函数并等待信号发送数据。对吗? @file-tracer 如果确定HashGen销毁时没有线程在运行,则无需使用QFutureWatcher。不像finished()resultReadyAt()信号,可以得到结果只需一步即可使用自定义信号。 QFutureWatcher 的某些功能不适用于QtConcurrent::run,例如cancel(),所以我认为它与其他QtConcurrent 功能更有吸引力。 谢谢,另外一件事,我想在按下按钮后从我的主线程(gui)运行 HashGen 函数,但我不知道为什么我会按照你说的做,但是我的程序崩溃了,调试器说它是因为check_sequential_completed 信号...您能否在主线程的新线程中添加一个运行函数的示例并再次在主线程中获取它的输出?我是 qt 的新手,如果你能在这方面帮助我,我将不胜感激 @file-tracer 我刚刚从我的一个项目中添加了一个示例并对其进行了测试。它工作正常。我不知道你的程序为什么会崩溃。对不起。

以上是关于从 std::tuple 函数 QtConcurrentRun 获取(多)返回值的主要内容,如果未能解决你的问题,请参考以下文章

创建 std::promise<std::tuple<T>> 时出现错误 C2512(仅限 Visual Studio)

std::tuple

C++11元组了解

在std :: tuple中转换元素

为什么`std :: pair`允许使用用户定义的删除移动构造函数从类类型的右值初始化?

std::get(std::tuple)