Qt:让任意线程执行一个匿名函数

本文介绍了一种在Qt环境下实现跨线程调用的方法,该方法允许从主线程或其他线程指派任务到任意指定的目标线程执行,不同于QtConcurrent结合QThreadPool的方式。通过JasonQt_InvokeFromThread类提供的invoke和waitForInvoke接口,实现非阻塞和阻塞的跨线程调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本类主要功能是在当前线程(比如说主线程),指派任意一个线程(比如说某个工作线程)去执行一个匿名函数。


注意,这个和QtConcurrent配合QThreadPool不一样,QtConcurrent配合QThreadPool只能指派回调到QThreadPool中的线程。

而这个类可以指派一个回调到任意线程。


两个主要接口

JasonQt_InvokeFromThread::invoke:非阻塞,只是将回调放到队列中,等待执行

JasonQt_InvokeFromThread::waitForInvoke:阻塞,函数执行完成后才回返回


代码部分:


.h文件内容:

#include <QThread>
#include <QMap>
#include <QDebug>

class JasonQt_InvokeFromThreadHelper: public QObject
{
    Q_OBJECT

private:
    QMutex m_mutex;
    QList< std::function<void()> > m_waitCallbacks;

public:
    void addCallback(const std::function<void()> &callback);

public slots:
    void onRun();
};

class JasonQt_InvokeFromThread
{
private:
    static QMutex g_mutex;
    static QMap< QThread *, JasonQt_InvokeFromThreadHelper * > g_helpers;

public:
    static void invoke(QThread *thread, const std::function<void()> &callback);

    static void waitForInvoke(QThread *thread, const std::function<void()> &callback);
};

.cpp文件内容

// JasonQt_InvokeFromThreadHelper
void JasonQt_InvokeFromThreadHelper::addCallback(const std::function<void ()> &callback)
{
    m_mutex.lock();
    m_waitCallbacks.push_back(callback);
    m_mutex.unlock();
}

void JasonQt_InvokeFromThreadHelper::onRun()
{
    if(!m_waitCallbacks.isEmpty())
    {
        m_mutex.lock();

        auto callback = m_waitCallbacks.first();
        m_waitCallbacks.pop_front();

        m_mutex.unlock();

        callback();
    }
}

// JasonQt_InvokeFromThread
QMutex JasonQt_InvokeFromThread::g_mutex;
QMap< QThread *, JasonQt_InvokeFromThreadHelper * > JasonQt_InvokeFromThread::g_helpers;

void JasonQt_InvokeFromThread::invoke(QThread *thread, const std::function<void ()> &callback)
{
    if(!(thread->isRunning()))
    {
        qDebug() << "JasonQt_InvokeFromThread::invoke: Target thread" << thread << "is not running!";
        return;
    }

    g_mutex.lock();

    auto it = g_helpers.find(thread);

    if(it == g_helpers.end())
    {
        auto helper = new JasonQt_InvokeFromThreadHelper;
        helper->moveToThread(thread);

        QObject::connect(thread, &QThread::finished, [=]()
        {
            g_mutex.lock();

            auto it = g_helpers.find(thread);
            if(it != g_helpers.end())
            {
                g_helpers.erase(it);
            }

            g_mutex.unlock();
        });

        g_helpers.insert( thread, helper );
        it = g_helpers.find(thread);
    }

    it.value()->addCallback(callback);

    QMetaObject::invokeMethod(it.value(), "onRun", Qt::QueuedConnection);

    g_mutex.unlock();
}

void JasonQt_InvokeFromThread::waitForInvoke(QThread *thread, const std::function<void ()> &callback)
{
    if(!(thread->isRunning()))
    {
        qDebug() << "JasonQt_InvokeFromThread::waitForInvoke: Target thread" << thread << "is not running!";
        return;
    }

    g_mutex.lock();

    auto it = g_helpers.find(thread);

    if(it == g_helpers.end())
    {
        auto helper = new JasonQt_InvokeFromThreadHelper;
        helper->moveToThread(thread);

        QObject::connect(thread, &QThread::finished, [=]()
        {
            g_mutex.lock();

            auto it = g_helpers.find(thread);
            if(it != g_helpers.end())
            {
                g_helpers.erase(it);
            }

            g_mutex.unlock();
        });

        g_helpers.insert( thread, helper );
        it = g_helpers.find(thread);
    }

    it.value()->addCallback([&]()
    {
        g_mutex.unlock();
        callback();
    });

    QMetaObject::invokeMethod(it.value(), "onRun", Qt::QueuedConnection);

    g_mutex.lock();
    g_mutex.unlock();
}

测试代码:

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

    qDebug() << "Main thread:" << QThread::currentThread();

    constexpr auto threadCount = 5;
    QVector< QObject * > objects;
    QThreadPool threadPool;

    objects.resize(threadCount);
    threadPool.setMaxThreadCount(threadCount);

    for(auto i = 0; i < threadCount; i++)
    {
        QtConcurrent::run([&objects, i]()
        {
            objects[i] = new QObject;

            qDebug() << "Thread started:" << QThread::currentThread();

            QEventLoop().exec();
        });
    }

    // 等待测试线程启动
    QThread::sleep(3);

    // 调用
    for(auto i = 0; i < (threadCount * 2); i++)
    {
        // 第一个参数是目标线程,第二个是回调
        JasonQt_InvokeFromThread::invoke(objects[i % threadCount]->thread(), [=]()
        {
            qDebug() << "Current thread:" << QThread::currentThread() << ", Flag:" << i;
        });
    }

    return a.exec();
}

执行输出

Main thread: QThread(0x7f821951bf30)
Thread started: QThread(0x7f8219705ac0, name = "Thread (pooled)")
Thread started: QThread(0x7f8219705f90, name = "Thread (pooled)")
Thread started: QThread(0x7f82197055f0, name = "Thread (pooled)")
Thread started: QThread(0x7f8219705120, name = "Thread (pooled)")
Thread started: QThread(0x7f8219704950, name = "Thread (pooled)")
Current thread: QThread(0x7f8219704950, name = "Thread (pooled)") , Flag: 0
Current thread: QThread(0x7f8219705ac0, name = "Thread (pooled)") , Flag: 3
Current thread: QThread(0x7f8219705120, name = "Thread (pooled)") , Flag: 1
Current thread: QThread(0x7f82197055f0, name = "Thread (pooled)") , Flag: 2
Current thread: QThread(0x7f8219705f90, name = "Thread (pooled)") , Flag: 4
Current thread: QThread(0x7f8219704950, name = "Thread (pooled)") , Flag: 5
Current thread: QThread(0x7f8219705ac0, name = "Thread (pooled)") , Flag: 8
Current thread: QThread(0x7f8219705120, name = "Thread (pooled)") , Flag: 6
Current thread: QThread(0x7f8219705f90, name = "Thread (pooled)") , Flag: 9
Current thread: QThread(0x7f82197055f0, name = "Thread (pooled)") , Flag: 7

可以看见,回调被执行在了测试线程中。


注:目标线程需要有运行Qt的事件循环,这是必须的。



<think>好的,我现在需要回答用户关于Qt C++中connect函数的问题,具体是“连接到信号可以直接执行,不连接槽函数”。首先,用户可能是在问是否可以在没有槽函数的情况下,直接将信号连接到某个执行代码,而不需要定义槽函数。 首先,我得回忆一下Qt的信号槽机制。通常,connect函数是将信号连接到槽函数,当信号被发射时,槽函数会被调用。但用户的问题是说是否可以不连接槽函数,直接让信号触发执行一些代码。这可能涉及到使用lambda表达式或者函数指针的方式,在connect的时候直接编写执行的代码,而不需要单独定义一个槽函数。 接下来,我需要确认在Qt中是否允许这样做。根据我的知识,从Qt5开始,支持使用lambda表达式作为槽函数。这意味着在connect的时候,可以传入一个lambda,里面包含需要执行的代码,而不必在类中显式声明一个槽函数。例如: connect(sender, &Sender::signal, [=](){ // 直接在这里写要执行的代码 }); 这种情况下,用户不需要定义槽函数,而是直接将代码写在lambda里。这样当信号被发射时,lambda里的代码就会执行。这应该就是用户所说的“直接执行,不连接槽函数”的情况。 不过,用户的问题可能有更深的含义,比如是否可以在不连接任何槽的情况下,仅通过信号来触发某些操作。但根据Qt的设计,信号必须连接到某个接收者(可以是槽函数、另一个信号,或者lambda等),否则信号发射后不会有任何效果。因此,必须要有某种形式的连接,但可以不需要传统的槽函数,而是用lambda替代。 另外,需要考虑Qt的连接类型,比如直接连接和队列连接,这会影响代码执行线程环境。如果信号和接收者在同一个线程,直接连接会立即执行;不同线程则可能需要队列连接。但无论是哪种连接方式,都需要有一个接收的处理函数,即使这个处理函数是匿名的lambda。 可能用户看到的是某些代码示例,其中在connect中使用lambda,而没有显式的槽函数,因此产生疑问。这时候需要解释这种用法是可行的,并且是Qt5以后推荐的做法,因为它减少了需要声明的槽函数数量,使代码更简洁。 此外,还需要注意lambda中的上下文管理,比如捕获的变量是否有效,尤其是在多线程环境下,避免悬空指针等问题。同时,如果lambda需要访问类的成员变量或函数,可能需要正确管理对象的生命周期,防止在lambda执行时对象已被销毁。 总结来说,用户的疑问可能在于是否可以不使用槽函数而直接在connect中执行代码,而答案是可以的,通过lambda表达式实现。需要详细说明这种用法,并指出注意事项,比如lambda的使用、上下文管理、线程安全等。 可能的误区是用户可能认为信号可以直接触发代码而不需要任何连接,但实际上必须有一个连接的目标,只是这个目标可以是lambda表达式而不是传统的槽函数。因此需要澄清这一点,避免误解。 最后,给出具体的代码示例,说明如何正确使用lambda在connect中直接执行代码,以及需要注意的地方,比如捕获列表、this指针的使用等,确保用户能够正确应用这种方法。</think>在Qt框架中,**信号(Signal)必须通过`connect`与接收函数(槽函数或可调用对象)绑定才能触发执行**。但可以通过以下方式实现"不显式定义槽函数"的代码直接执行: --- ### 核心方法:**使用Lambda表达式作为匿名槽** $$示例代码:$$ ```cpp // 场景:点击按钮后直接执行代码,无需单独定义槽函数 connect(btn, &QPushButton::clicked, [=]() { qDebug() << "按钮被点击!"; this->doSomething(); // 可直接调用成员函数 }); ``` --- ### 关键特性解析 1. **Lambda代替槽函数** - 通过Lambda表达式直接编写响应代码 - 支持捕获局部变量(需注意生命周期管理): ```cpp int counter = 0; connect(btn, &QPushButton::clicked, [&counter]() { counter++; // 捕获外部变量 }); ``` 2. **信号参数传递** - Lambda可接收信号参数(需与信号签名匹配): ```cpp connect(scrollBar, &QScrollBar::valueChanged, [](int value) { qDebug() << "当前值:" << value; }); ``` 3. **线程安全性** - 使用`Qt::ConnectionType`控制执行线程: ```cpp connect(obj, &MyObject::dataReady, workerThread, [=](){ /* 跨线程操作 */ }, Qt::QueuedConnection); ``` --- ### 与传统槽函数的对比 | 特性 | Lambda方式 | 传统槽函数 | |---------------------|-------------------------------|--------------------------| | 代码位置 | 紧邻`connect`声明 | 需要单独类成员函数 | | 局部变量访问 | 支持捕获 | 需通过成员变量传递 | | 多信号复用 | 不同Lambda需重复编写 | 单一槽函数可响应多个信号 | | 维护性 | 适合简单逻辑 | 适合复杂业务逻辑 | --- ### 注意事项 1. **对象生命周期** - 若Lambda捕获`this`指针,需确保对象未被提前销毁 - 推荐使用`QPointer`防御性编程: ```cpp QPointer<MyClass> guard(this); connect(obj, &MyObject::signal, [guard]() { if (guard) guard->doWork(); }); ``` 2. **内存管理** - 跨线程Lambda避免捕获栈变量 - 异步操作建议使用智能指针: ```cpp auto task = QSharedPointer<MyTask>::create(); connect(obj, &MyObject::trigger, [task]() { task->executeAsync(); }); ``` 3. **性能优化** - 高频信号慎用Lambda,可能产生临时对象 - 可复用Lambda对象: ```cpp auto handler = [this]() { /* ... */ }; connect(btn1, &QPushButton::clicked, handler); connect(btn2, &QPushButton::clicked, handler); ``` --- ### 高级用法:**连接信号到任意可调用对象** ```cpp // 连接信号到静态函数 connect(obj, &MyObject::signal, &MyStaticHandler::handle); // 连接信号到std::function std::function<void()> callback = std::bind(&MyClass::method, this); connect(obj, &MyObject::signal, callback); // 连接信号到QFutureWatcher QFutureWatcher<void>* watcher = new QFutureWatcher<void>; connect(watcher, &QFutureWatcher<void>::finished, [=]() { watcher->deleteLater(); }); ``` --- ### 总结 虽然必须通过`connect`建立信号与执行代码的关联,但**Lambda表达式可以实现"无显式槽函数"的代码直接执行**。这种范式: 1. 适用于简单响应逻辑 2. 提升代码内聚性 3. 需谨慎管理对象生命周期 4. 在Qt5及以上版本推荐使用 对于复杂场景,仍需结合传统槽函数进行模块化设计。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值