invokeMethod和QThread和connect

函数简介 

QMetaObject::invokeMethod 是Qt框架提供的一种跨线程调用对象成员函数的方式,它利用了Qt的信号槽机制,确保了调用的安全性和线程间的同步。它的主要作用是在不同线程间传递消息或触发操作,确保这些操作在正确的线程上下文中执行,尤其是用于UI更新等场景,防止直接在非UI线程中修改UI组件导致的错误。

 
参数说明:

bool QMetaObject::invokeMethod(QObject *obj, const char *member, 
                            Qt::ConnectionType type, 
                            QGenericReturnArgument ret,
                            QGenericArgument val0 = QGenericArgument(nullptr), 
                            QGenericArgument val1 = QGenericArgument(), 
                            QGenericArgument val2 = QGenericArgument(), 
                            QGenericArgument val3 = QGenericArgument(), 
                            QGenericArgument val4 = QGenericArgument(),
                            QGenericArgument val5 = QGenericArgument(),
                            QGenericArgument val6 = QGenericArgument(), 
                            QGenericArgument val7 = QGenericArgument(),
                            QGenericArgument val8 = QGenericArgument(),
                            QGenericArgument val9 = QGenericArgument())

此函数用于调用对象的成员(信号或插槽)。如果可以调用成员,则返回true。如果没有此类成员或参数不匹配,则返回false。
QMetaObject::invokeMethod除上文这个函数以外还有5个重载函数,这里不再赘述。
obj:被调用对象的指针
member:成员方法的名称
type:连接方式,默认值为 Qt::AutoConnection

  • Qt::DirectConnection,则会立即调用该成员。(同步调用)
  • Qt::QueuedConnection,则会发送一个QEvent,并在应用程序进入主事件循环后立即调用该成员。(异步调用)
  • Qt::BlockingQueuedConnection,则将以与Qt :: QueuedConnection相同的方式调用该方法,除了当前线程将阻塞直到事件被传递。使用此连接类型在同一线程中的对象之间进行通信将导致死锁。(异步调用)
  • Qt::AutoConnection,则如果obj与调用者位于同一个线程中,则会同步调用该成员; 否则它将异步调用该成员。

ret:接收被调用函数的返回值
val0~val9:传入被调用函数的参数,最多十个参数
注:必须要使用Q_RETURN_ARG()宏来封装函数返回值、Q_ARG()宏来封装函数参数。

若一个对象obj有一个槽函数func(QString,int),返回值为bool,那么调用方式如下:

bool result;
//同步调用
QMetaObject::invokeMethod(obj, "func", Qt::DirectConnection,
                          Q_RETURN_ARG(bool, result),
                          Q_ARG(QString, "test"),
                          Q_ARG(int, 100);
//异步调用
QMetaObject::invokeMethod(obj, "func", Qt::QueuedConnection,
                          Q_ARG(QString, "test"),
                          Q_ARG(int, 100);

注:使用Qt::QueuedConnection异步调用,将无法获取返回值,因为此连接方式只是负责把事件交给事件队列,然后立刻返回,所以,函数返回值就无法确定了。


但,我们可以使用上文提及的Qt::BlockingQueuedConnection连接方式,这个连接方式会阻塞发射信号的线程一直等到队列连接槽返回后,才会恢复阻塞,这样就可以保证我们能得到函数的返回值。使用如下

bool result;
QMetaObject::invokeMethod(obj, "func", Qt::BlockingQueuedConnection,
                          Q_RETURN_ARG(bool, result),
                          Q_ARG(QString, "test"),
                          Q_ARG(int, 100);


需要注意的是,qt官方文档中的提醒:使用此连接类型在同一线程中的对象之间进行通信将导致死锁

最后,因为连接方式type默认值为Qt::AutoConnection,所以当被调用的obj与调用者不在同一线程中,可以直接调用:

//此Tcpserver为一个独立线程
//在主程序中调用reportImageResult(),因为TcpServer对象与调用者主线程不在同一线程中。
//Qt::AutoConnection此连接方式将会自动以异步的方式调用
void TcpServer::reportImageResult(int imgId, const QImage &image, int result)
{
    QMetaObject::invokeMethod(this, "reportImageResultAsync",
                              Q_ARG(int, imgId),
                              Q_ARG(QImage, image),
                              Q_ARG(int, result);
}

使用QMetaObject::invokeMethod来调用函数时,当函数的参数有自定义类型时,程序将会报错,因为调用的类型必须是信号、槽,以及Qt元对象系统能识别的类型。可以使用Qt命名类型所提供的qRegisterMetaType()来注册自定义类型。

invokeMethod和QThread

关于QMetaObject::invokeMethodQThread的关系,两者服务于不同的目的:

  • QMetaObject::invokeMethod:
    • 目的:主要用于线程间的通信和同步,确保安全地在另一个线程中执行函数。
    • 特点
      • 轻量级的线程间通信手段。
      • 支持直接调用成员函数或槽函数,可以是直接调用也可以是队列调用(通过Qt::QueuedConnection)。
      • 不直接涉及线程的创建或管理,而是利用已存在的线程机制进行交互。
  • QThread:
    • 目的:用于实现多线程执行,允许开发者将任务分配到不同的线程中并行处理,以提升性能或避免阻塞UI线程。
    • 特点
      • 提供了线程的创建、管理和销毁机制。
      • 通常需要配合对象的moveToThread方法将对象转移到新线程中执行。
      • 支持重写run方法来定义线程执行的具体任务。

总结来说,QMetaObject::invokeMethod是多线程应用中用于线程间通信的重要工具,而QThread是用来创建和管理线程的。两者相辅相成,在实现多线程应用时往往需要结合使用。


示例如下:

//头文件
#include <QMetaType>

//自定义类型
struct AsynResults {
    int imgId;
    QImage image;
    int result;
};

//在类构造时,注册类型
qRegisterMetaType<AsynResults>("AsynResults");

//QMetaObject::invokeMethod调用
QMetaObject::invokeMethod(this, "reportImageResultAsync",
                              Q_ARG(AsynResults, asynresults);

以上 原文链接:https://www.jianshu.com/p/c77e28a81708

代码示例

报错

D:\sunnyTemp\qt\Demo\invokeMethod\invokeMethod\widget.cpp:31: error: no matching function for call to 'QMetaObject::invokeMethod(Widget*, Widget::onTaskFinished(const QString&)::<lambda()>, Qt::ConnectionType)' }, Qt::QueuedConnection);

当前 qt 5.9版本 不支持lambda表达式

这个编译错误表明在尝试调用QMetaObject::invokeMethod时,传入的第二个参数(即槽函数)不符合预期。在提供的代码片段中,您似乎尝试使用了一个lambda表达式作为槽函数,但QMetaObject::invokeMethod期望的是一个成员函数指针或者一个全局函数。Lambda表达式不能直接用作槽函数的参数,特别是在这种直接调用QMetaObject::invokeMethod的形式中。

原始代码


void Widget::onTaskFinished(const QString &result) {
    
    QMetaObject::invokeMethod(this, [this, result](){
        resultLabel->setText(result);
    }, Qt::QueuedConnection);
}

修改为QT5 lambda

void Widget::onTaskFinished(const QString &result) {
    // ...
    QMetaObject::invokeMethod(this, &Widget::updateUI, Qt::QueuedConnection, Q_ARG(QString, result));
    // 或者使用Qt5的新语法(如果可用)
    QMetaObject::invokeMethod(this, [this](const QString &res){ updateUI(res); }, Qt::QueuedConnection, result);
    // ...
}

还是 不可以用 可能qt5.9 没办法使用吧

修改为标准形式

void Widget::onTaskFinished(const QString &result)
{
    // 使用invokeMethod确保在主线程更新界面

    QMetaObject::invokeMethod(this, "updateUI",Q_ARG(QString, result));
}

void Widget::updateUI(const QString &result)
{
        resultLabel->setText("current value:" + result);
}

运行结果

value值 只变化一次 

当你在QThread的子类中重写了run()方法并放置了任务代码,run函数里代码执行完 ,就会发送finished信号了

QMetaObject::invokeMethod和QObject::connect

QMetaObject::invokeMethod 和普通信号(signal)的区别在于它们的用途和使用方式。以下是对这两者的详细比较:

QMetaObject::invokeMethod

QMetaObject::invokeMethod 是一个静态方法,用于在运行时调用对象的方法。它的主要特点包括:

  1. 运行时调用invokeMethod 可以在运行时动态地调用对象的方法。这对于需要在运行时决定调用哪个方法的情况非常有用。

  2. 参数传递invokeMethod 可以传递参数给目标方法。

  3. 返回值invokeMethod 可以获取目标方法的返回值。

  4. 线程上下文invokeMethod 可以指定调用方法的线程上下文(同步调用或异步调用)。

以下是一个使用 QMetaObject::invokeMethod 的示例:

#include <QCoreApplication>
#include <QObject>
#include <QMetaObject>
#include <QDebug>

class MyObject : public QObject {
    Q_OBJECT
public slots:
    void mySlot(const QString &message) {
        qDebug() << "Slot called with message:" << message;
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    MyObject obj;
    QMetaObject::invokeMethod(&obj, "mySlot", Q_ARG(QString, "Hello, world!"));

    return app.exec();
}

QObject::connect

用途

  • 信号和槽机制:用于对象之间的通信,当一个对象状态变化时通知另一个对象。
  • 松耦合:对象之间的耦合度低,通过信号和槽连接,无需对象之间直接引用。
  • 类型安全:在编译时检查信号和槽的参数类型匹配。

适用环境

  • 事件驱动编程:用于实现事件驱动编程模型,典型的 GUI 应用程序中,用户操作触发信号,槽响应信号进行处理。
  • 对象间通信:在对象之间传递数据或通知状态变化。
  • 跨线程通信:通过 Qt::QueuedConnection 实现线程安全的信号和槽连接,确保在不同线程之间正确传递信号。
    #include <QCoreApplication>
    #include <QObject>
    #include <QDebug>
    
    class MyObject : public QObject {
        Q_OBJECT
    public:
        void sendMessage(const QString &message) {
            emit mySignal(message);
        }
    
    signals:
        void mySignal(const QString &message);
    
    public slots:
        void mySlot(const QString &message) {
            qDebug() << "Slot called with message:" << message;
        }
    };
    
    int main(int argc, char *argv[]) {
        QCoreApplication app(argc, argv);
    
        MyObject obj;
        QObject::connect(&obj, &MyObject::mySignal, &obj, &MyObject::mySlot);
        obj.sendMessage("Hello, world!");
    
        return app.exec();
    }
    

    总结

  • QMetaObject::invokeMethod

    • 用于动态调用对象的方法。
    • 可在运行时决定调用哪个方法。
    • 支持异步操作,适合跨线程调用。
    • 适合需要反射机制的场景。
  • QObject::connect

    • 用于信号和槽的连接,实现对象之间的通信。
    • 适合事件驱动编程模型和对象间的松耦合通信。
    • 类型安全,编译时检查参数匹配。
    • 适合跨线程的信号和槽通信。

动态调用和静态调用

动态调用 是指在程序运行时,根据特定条件或输入来决定调用哪个对象的方法。这种方式通常使用反射或类似的机制来实现。动态调用允许程序在运行时具有更大的灵活性和适应性。

在 Qt 中,QMetaObject::invokeMethod 提供了这种动态调用的能力。你可以在运行时根据字符串名称或其他条件决定调用哪个方法,并且可以传递参数和获取返回值。

QString methodName = "myMethod"; // 使用QMetaObject::invokeMethod进行动态调用 QMetaObject::invokeMethod(&obj, methodName.toUtf8().constData());

可以改变methodName的值 从而执行 不同函数

静态调用对象的方法

静态调用 是指在程序编写时就已经明确地指定了要调用的对象的方法。编译器会在编译时检查这些调用的正确性,包括参数类型、方法的存在性等。静态调用通常是直接的函数调用或对象方法调用。

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值