函数简介
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::invokeMethod
和QThread
的关系,两者服务于不同的目的:
- 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
是一个静态方法,用于在运行时调用对象的方法。它的主要特点包括:
-
运行时调用:
invokeMethod
可以在运行时动态地调用对象的方法。这对于需要在运行时决定调用哪个方法的情况非常有用。 -
参数传递:
invokeMethod
可以传递参数给目标方法。 -
返回值:
invokeMethod
可以获取目标方法的返回值。 -
线程上下文:
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的值 从而执行 不同函数
静态调用对象的方法
静态调用 是指在程序编写时就已经明确地指定了要调用的对象的方法。编译器会在编译时检查这些调用的正确性,包括参数类型、方法的存在性等。静态调用通常是直接的函数调用或对象方法调用。