基于Qt线程另类使用方式。

Qt中与线程有关的类: QThread、QThreadPool、 QMutex、 QWaitCondition、 QtConCurrent、QFuture、QFutureWacher等等。

这些基本能满足对于线程的所有需求,但是使用方式偏于复杂,我在线程的基础上融入了C++11的lambda算子,使其调用变得简单,方便。

例如我在工作中一个第三方库不断持续的回调最终调用到我类中的一个方法,我需要传入一个图片,而我需要使用QWidget.RenderWidget函数截取一个窗口的

内容,可是写完运行就会报错,不能在线程中调用GUI相关的方法,也就是说这个函数只能再主线程调用,于是我只能再主线程启动定时器不停的截取图片,

并将图片存为局部变量,线程中再直接给截取好的图片,可是因为这样操作一番,导致效率就降低很多。

1. 因为线程不问我要的时候,我的定时器还是在持续的截取,而且是高频(30ms截取一次,因为我并不确定线程什么时候问我要,什么时候不要,但我

   需要给最新的图片)

2. 即便问我要,我也无法保证是一一对应的,也许线程连续两次问我要的图片是同一次截取,也许我截取了2次,线程只问我要了一次,这无疑也是

   对CPU无谓的消耗。

于是我就想仅仅因为这一行代码,导致我加了很多的逻辑,最终导致代码的业务逻辑变得非常复杂,可读性非常差,可又必须这么做,有没有什么方法

可是将这一行代码直接抛给主线程执行呢? 于是我使用Lambda算子进行了封装:

class InvokerHelper : public QObject , public Instance<InvokerHelper>
{
    Q_OBJECT

public:

    typedef std::function<void()> Action;

public:

    InvokerHelper(QObject * parent = Q_NULLPTR);     
    void start();
    void quit();
    ~InvokerHelper();
    void execute(const Action& action);
    void waitExecute(const Action& action, unsigned long time = ULONG_MAX);

private slots:

    void slot_executeAction(const Action& action);

private:

    QThread*  _currThread;
    bool      _bStart;

};

其中Instance实现:

template<typename T>
class Instance
{
public:
    
    static T& instance()
    {
        static T ret;
        return ret;
    }
};

源码如下:

InvokerHelper::InvokerHelper(QObject * parent) 
    : QObject(parent),_bStart(false)
{
    qRegisterMetaType<Action>("Action");
    _currThread = QThread::currentThread();
}

InvokerHelper::~InvokerHelper() 
{
}

void InvokerHelper::start()
{
    _bStart = true;
}

void InvokerHelper::quit()
{
    _bStart = false;
}

void InvokerHelper::execute(const Action& action)
{
    QMetaObject::invokeMethod(this, "slot_executeAction", Qt::AutoConnection,
        Q_ARG(Action, action));
}

void InvokerHelper::waitExecute(const Action & action, unsigned long time)
{
    if (QThread::currentThread() == _currThread)
    {
        if (!_bStart)
            return;
        action();
        return;
    }
    QWaitCondition wait;
    execute([&wait, action]() 
    {
        action(); 
        wait.wakeOne(); 
    });
    QMutex mutex;
    mutex.lock();
    wait.wait(&mutex, time);
    mutex.unlock();
}

void InvokerHelper::slot_executeAction(const Action& action)
{
    if (!_bStart)
        return;
    if(action)
        action();
}

并且定义这样两个宏:

#define InvokeMethod InvokerHelper::instance().execute
#define InvokeWait InvokerHelper::instance().waitExecute

在实际使用时,在主线程中调用:

InvokerHelper::instance().start();
InvokerHelper::instance().quit();

分别是启动与退出,看实现其实就是修改一个变量值,目的是防止主线程退出的时候,

其它线程还在往主线程抛东西,这样会导致崩溃。

如果在main函数的开始或者结束掉用这行代码,那么在程序运行过程,就可以在任何时候将线程中

的代码抛到主线程并且执行。仅仅只需要对需要抛送的代码进行这样包装:

InvokeMethod([]() //这个只是抛送,并不会等待执行完成
{
    //这里填充你需要在主线程执行的代码,注意lambda算子引用参数的生命周期
});

InvokeWait([]() //这个既抛送到主线程,同时等待执行完成
{
});

 

InvokerHelper的实现原理就是使用QMetaObject的invokeMethod。

QMetaObject::invokeMethod(this, "slot_executeAction", Qt::AutoConnection,Q_ARG(Action, action));
//这是Qt提供的函数,其它线程调用这个函数时,会跳转到this所在线程去执行对应的槽函数"slot_executeAction"。 所以一定需要在主线程去调用start()

上面这个类提供了可以在任意其它线程(自己线程也行,但不需要,除非你自己不确定)将代码抛送到主线程的方法。

其实根据上述代码的注释可以知道,抛送到主线程关键在于,InvokeHelper这个单例是在主线程中申明的。

如果我们在一个有消息队列线程中声明一个InvokeHelper对象,那么就可以将任意其它线程的代码抛送到该线程中。于是有下面这样一个类:

class KThreadHelper : public QThread, public Instance<KThreadHelper>
{
    Q_OBJECT

public:
    
    typedef InvokerHelper::Action Action;

public:

    KThreadHelper(QObject* parent = Q_NULLPTR);
    void execute(const Action& action);
    void waitExecute(const Action& action, unsigned long time = ULONG_MAX);

protected:

    void run();

private:

    InvokerHelper*    _invokeHelper;

};

.cpp

KThreadHelper::KThreadHelper(QObject* parent)
    :QThread(parent),_invokeHelper(nullptr)
{
}

void KThreadHelper::execute(const Action & action)
{
    if (!isRunning())
        return;
    _invokeHelper->execute(action);    
}

void KThreadHelper::waitExecute(const Action & action, unsigned long time)
{
    if (!isRunning())
        return;
    _invokeHelper->waitExecute(action, time);
}

void KThreadHelper::run()
{
    InvokerHelper __helper;
    _invokeHelper = &__helper;
    _invokeHelper->start();
    exec();
}

同样在主线调用:

KThreadHelper::start()
KThreadHelper::quit()

完成这种抛送机制启动以及关闭,这样我们可以在任意其它线程将指定代码抛送线程中执行。

例如:对于一些卡界面的行为,就可以直接将其抛送线程中执行。

同样定义了两个宏

#define ThreadCall KThreadHelper::instance().execute
#define ThreadWait KThreadHelper::instance().waitExecute

 

值得注意的是,在使用lambda算子时,由于这是惰性函数,lambda算子引用的参数一定需要注意其生命周期。

所以专门封装一个类用来判断指针的有效性。

typedef QSet<void*> SafePoints;
class TheSafePoints : public SafePoints, public Instance<TheSafePoints>
{
public:

    template<typename T>
    void add(T* t)
    {
        QWriteLocker lock(&_lock);
        insert(t);
    }

    template<typename T>
    void cut(T* t)
    {
        QWriteLocker lock(&_lock);
        remove(t);
    }

    template<typename T>
    bool has(T* t)
    {
        QReadLocker lock(&_lock);
        return contains(t);
    }

    QReadWriteLock _lock;
};

static QReadWriteLock __g_lock;
template<typename T>
struct SafeDelegate
{
    SafeDelegate()
    {
        TheSafePoints::instance().add((T*)this);
    }

    ~SafeDelegate()
    {
        TheSafePoints::instance().cut((T*)this);
    }
};

#define SafeCheck(t) if(!TheSafePoints::instance().has(t)) return

当一个类继承自SafeDelegate时,如果该类的对象被析构了,使用SafeCheck对应的指针可以对该指针进行安全检查。

posted @ 2018-11-08 17:51  点苍苔1990  阅读(316)  评论(0编辑  收藏  举报