• QTimer::singleShot问题及用法


    问题描述

    问题描述:QTimer::singleShot定时器事件超时,如果此时类内对象已经被回收,定时器事件调用已经释放的类内资源时会引起崩溃

    void func()
    {
        QTimer::singleShot(50,[=](){
            this->continueNodeTask();
        });
    }
    
    /*
    如果singleShot事件已经注册,当前类对象已经回收,定时时间到期后相应类对象已经回收,调用会引起指针错误
    核心原因是 事件和类的生命周期不一致
    应当保持类内事件与类内对象声明周期保持一致
    */
    
    //QTimer::singleShot等价写法
    QTimer* t = new QTimer(this);
    connect(t,QTimer::timeout,[=](){
        t->stop();
        t->deleteLater();
    
        this->continueNodeTask();
    });
    t->start(50);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    以上写法能实现singleShot但是太过臃肿,经过仔细发现QTimer有相应的处理方式

    singleShot使用需要注意的点

    singleShot有很多重载函数,各有自己的使用场景,使用不当,容易导致奔溃或者达不到预期。

    1、时间精度:

    查看源码可知,调用的时候若没有传入Qt::TimerType参数,传入的超时时间小于等于2000ms时,QT自动选择使用精度更高的时钟类型:Qt::PreciseTimer,否则使用精度次高的Qt::CoarseTimer

    void QTimer::singleShot(int msec, const QObject *receiver, const char *member)
    {
        // coarse timers are worst in their first firing
        // so we prefer a high precision timer for something that happens only once
        // unless the timeout is too big, in which case we go for coarse anyway
        singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, receiver, member);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、不恰当使用lambda表达式导致奔溃

    如下为singleShot使用lambda的两种常用方式,区别在于第二个参数,为了访问类成员,lambda都捕获this指针。

    方式1:若对话框对象在超时前已经销毁,则超时时会调用lambda,而lambda捕获了this指针,这时导致奔溃。

    方式2:第二个参数传入this指针,若对话框对象在超时前销毁,超时时间到了也不会调用lambda,所以不会奔溃。

    void TestDlg::on_pushButton_clicked()
    {    
        //方式1
        QTimer::singleShot(2000, [this]()
        {
            //访问类成员
        });
        
        //方式2
        QTimer::singleShot(2000, this, [this]()
        {
            //访问类成员
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    见Qt助手说明:

    [static] void QTimer::singleShot(int msec, const QObject *receiver, PointerToMemberFunction method)
    This is an overloaded function.
    This static function calls a member function of a QObject after a given time interval.
    It is very convenient to use this function because you do not need to bother with a timerEvent or create a local QTimer object.
    The receiver is the receiving object and the method is the member function. The time interval is msec milliseconds.
    If receiver is destroyed before the interval occurs, the method will not be called. The function will be run in the thread of receiver. The receiver's thread must have a running Qt event loop.
    Note: This function is reentrant.
    This function was introduced in Qt 5.4.
    See also start().
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    If receiver is destroyed before the interval occurs, the method will not be called

    如果接收者在定时时间到达之前被销毁,相应的方法不会调用

    1、第一种超时到后仍然会调用lambda

    QTimer::singleShot(2000,[this](){
        this->doSomething();
    });
    //若对话框对象在超时前已经销毁,则超时时会调用lambda,而lambda捕获了this指针,这时导致奔溃
    
    //函数原型
    template 
        static inline typename std::enable_if::IsPointerToMemberFunction &&
                                              !std::is_same::value, void>::type
                singleShot(Duration interval, Qt::TimerType timerType, const QObject *context, Func1 slot)
        {
            //compilation error if the slot has arguments.
            typedef QtPrivate::FunctionPointer SlotType;
            Q_STATIC_ASSERT_X(int(SlotType::ArgumentCount) <= 0,  "The slot must not have any arguments.");
    
            singleShotImpl(interval, timerType, context,
                           new QtPrivate::QFunctorSlotObject::Value, void>(std::move(slot)));
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2、第二种超时到之前receiver已经被销毁则不会调用lambda

    QTimer::singleShot(2000,this,[this](){
        this->doSomething();
    });
    
    //若对话框对象在超时前已经销毁,则超时时不会调用lambda,
    
    //函数原型
    static void singleShot(int msec, const QObject *receiver, const char *member);
    
    //内部调用过程
    /*!
        \reentrant
        This static function calls a slot after a given time interval.
        It is very convenient to use this function because you do not need
        to bother with a \l{QObject::timerEvent()}{timerEvent} or
        create a local QTimer object.
        Example:
        \snippet code/src_corelib_kernel_qtimer.cpp 0
        This sample program automatically terminates after 10 minutes
        (600,000 milliseconds).
        The \a receiver is the receiving object and the \a member is the
        slot. The time interval is \a msec milliseconds.
        \sa start()
    */
    void QTimer::singleShot(int msec, const QObject *receiver, const char *member)
    {
        // coarse timers are worst in their first firing
        // so we prefer a high precision timer for something that happens only once
        // unless the timeout is too big, in which case we go for coarse anyway
        singleShot(msec, msec >= 2000 ? Qt::CoarseTimer : Qt::PreciseTimer, receiver, member);
    }
    
    
    QSingleShotTimer::QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char *member)
        : QObject(QAbstractEventDispatcher::instance()), hasValidReceiver(true), slotObj(nullptr)
    {
        timerId = startTimer(msec, timerType);
        connect(this, SIGNAL(timeout()), r, member);
    }
    
    class QSingleShotTimer : public QObject
    {
        Q_OBJECT
        int timerId;
        bool hasValidReceiver;
        QPointer receiver;
        QtPrivate::QSlotObjectBase *slotObj;
    public:
        ~QSingleShotTimer();
        QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, const char * m);
        QSingleShotTimer(int msec, Qt::TimerType timerType, const QObject *r, QtPrivate::QSlotObjectBase *slotObj);
    Q_SIGNALS:
        void timeout();
    protected:
        void timerEvent(QTimerEvent *) override;
    };
    
    void QSingleShotTimer::timerEvent(QTimerEvent *)
    {
        // need to kill the timer _before_ we emit timeout() in case the
        // slot connected to timeout calls processEvents()
        if (timerId > 0)
            killTimer(timerId);
        timerId = -1;
        if (slotObj) {
            // If the receiver was destroyed, skip this part
            if (Q_LIKELY(!receiver.isNull() || !hasValidReceiver)) {
                // We allocate only the return type - we previously checked the function had
                // no arguments.
                void *args[1] = { nullptr };
                slotObj->call(const_cast(receiver.data()), args);
            }
        } else {
            emit timeout();
        }
        // we would like to use delete later here, but it feels like a
        // waste to post a new event to handle this event, so we just unset the flag
        // and explicitly delete...
        qDeleteInEventHandler(this);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
  • 相关阅读:
    【Linux】 linux | docker | 安装nacos
    一个讲座监控软件
    SpringBoot整合原理解析
    2022开源大数据热力报告总结
    数据结构 第二章作业 线性表 西安石油大学
    跨境电商网站建设 三大跨境品牌厂商综合评测
    使用docker安装MySQL,Redis,Nacos,Consul教程
    轻取软考45分之软考信息系统项目管理师范围管理​章节学习笔记
    MySQL第十二讲:ShardingJDBC详解
    团队协作中如何处理ConflictingBeanDefinitionException异常
  • 原文地址:https://blog.csdn.net/qq_20999867/article/details/126361562