Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Assert failure: cannot send event to object owned by other thread; Debug only error in msvc using qthreadpool and qrunnable



  • hello dear qt people,

    currently i am facing a problem with my msvc17 x64 compiler in debug mode with my qt gui application (qt version 5.12.2 on win10, c++17).

    i am using an qtimer (in QWidgetClassInMainThread class) to call every ~20 to 50ms the method QWidgetClassInMainThread::startImageProcessingIteration().
    that method should create an qrunnable derived object getting passed an opencv mat image in the constructor.

    this qrunnable will be passed later to an qthreadpool to do some calculations and drawings on the opencv image.
    in the end the opencv image will be converted to an qimage in the reimplemented run() and an signal should emit the qimage back to the main thread where the qimage will be shown in gui (it is possible that further emits in run() have to be implemented later).

    because of the frequent call of the method by the qtimer and the need to start threads again and again, i decided to use the threadpool.

    unfortunately while running the application in debug mode the program crashes on start with the error message:

    ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread ...
    

    the strange thing is that when starting the program in release mode it runs without any problems and errors.

    i tried to change the setAutoDelete() value of the qrunnable in QWidgetClassInMainThread::startImageProcessingIteration(), with more or less success (see comment in code).

    now i am a bit clueless and hope any qt pros can give me some tips or suggestions ;) thank you guys!

    Code (shortened):

    QWidgetClassInMainThread.hpp

    class QWidgetClassInMainThread : public QWidget
    {
        Q_OBJECT
    
    private:
        QLabel label{this};
        QPixmap pixmap{};
    
        QTimer imageTimer{this};
    
        QThreadPool threadpool{this};
    
        void startImageProcessingIteration();
    
        void setImageInQLabel(QImage);
    
    public:
        QWidgetClassInMainThread(QWidget *parent = nullptr);
    };
    

    QWidgetClassInMainThread.cpp

    Q_DECLARE_METATYPE(QImage);
    
    QWidgetClassInMainThread::QWidgetClassInMainThread(QWidget *parent)
        : QWidget(parent)
    {
        qRegisterMetaType<QImage>();
    
        // connect qtimer to startImageProcessingIteration()
        connect(&imageTimer, &QTimer::timeout, this, &QWidgetClassInMainThread::startImageProcessingIteration);
    }
    
    // method called every ~20ms to ~50ms by qtimer
    void QWidgetClassInMainThread::startImageProcessingIteration()
    {
        // create new qrunnable and pass cloned opencv mat image, from camera object, in constructor
        auto *iteration = new CalculationRunnable(this, camera.getCurrentOpenCVImage());
    
        // connect emit from iteration object with setImageInQLabel()
        connect(iteration, &CalculationRunnable::sendImageToMainThread, this, &QWidgetClassInMainThread::setImageInQLabel);
    
        // VARIANT 1
        iteration->setAutoDelete(true); // error with msvc in debug mode, AND ONLY in debug mode! -> assert failure ... cannot send event to object owned by other thread ...
        // VARIANT 2
        iteration->setAutoDelete(false); // no errors but what todo with the iteration pointer variable because it will not be deleted by the threadpool?
    
        // put qrunnable in threadpool
        threadpool.start(iteration);
    }
    
    void QWidgetClassInMainThread::setImageInQLabel(QImage qImage)
    {
        // update qpixmap and qlabel in main thread
        pixmap.convertFromImage(qImage);
        label.setPixmap(_pix);
    }
    

    CalculationRunnable.hpp

    class CalculationRunnable : public QObject, public QRunnable
    {
        Q_OBJECT
    
    private:
        cv::Mat openCVImage;
    
    public:
        CalculationRunnable(QObject *parent, cv::Mat openCVImage);
    
    public:
        void run() override;
    
    signals:
        void sendImageToMainThread(QImage);
    };
    

    CalculationRunnable.cpp

    CalculationRunnable::CalculationRunnable(QObject *parent, cv::Mat openCVImage)
        : QObject (parent)
        , QRunnable ()
        , openCVImage(openCVImage)
    {}
    
    void CalculationRunnable::run()
    {
        auto image = openCVImage;
    
        /*
         * Do something with cv image.
         * do other emits.
         */
    
        // convert OpenCV image to QImage
        QImage qimage = cvMatToQImage(image);
    
        // send signal with qimage to main thread to update qpixmap/qlabel
        emit sendImageToMainThread(qimage);
    }
    

  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Since you make it deletable, don't pass a parent to it.

    One other thing, for all QObject based variables that you keep on stack, don't set the parent. This will trigger a double delete. Once because to the parent/child relationship and once because of the destruction happening when going out of scope.



  • thanks for your reply and sorry for long time to answer.

    so you suggest to create the qrunnable that way:

    // no this in ctor
    auto *iteration = new CalculationRunnable(nullptr, camera.getCurrentOpenCVImage());
    
    ...
    
    iteration->setAutoDelete(true);
    threadpool.start(iteration);
    

    and about your second recommendation, do you mean i should not pass this in the class variables:

    class QWidgetClassInMainThread : public QWidget
    {
     ...
        QTimer imageTimer{}; // no this
    
        QThreadPool threadpool{}; // no this
    ...
    

  • Lifetime Qt Champion

    Class members are "local" variable so they will get destroy on instance destruction. If you give a parent to these objects, then they will also be part of the child tree of the widget and deletion will also be triggered on them.


Log in to reply