从线程/禁用断言重绘闪屏

时间:2015-03-06 18:28:05

标签: c++ multithreading qt qtimer qsplashscreen

问题

我想使用QTimer来更新绘制进度条的派生QSplashScreen(使用绘图命令,而不是窗口小部件)来估计程序何时开始运行。

必要时,这会在exec调用QCoreApplication之前发生。通过在第二个线程中放置一个计时器,并在启动屏幕中调用一个更新进度的函数,我已经通过在X11和Windows 上的工作(仅在发布模式下)重新绘制小部件。但是,这不会在调试模式下工作,因为它会产生以下错误:

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

我并不是真的担心这个断言,因为代码在发布时并没有崩溃,而且它只是一个闪屏,但是我需要能够在调试中运行程序,所以I&I #39; d喜欢a)重构代码,以便它不会触发断言,或b)不能使用这个特定的断言。

尝试:

  • 使用update()代替repaint()。这不会导致断言,但它也不会重新绘制,因为主线程太忙于加载共享库等,并且定时器事件不会被处理,直到I'我准备在启动画面上调用finish
  • 在主循环中启动QTimer。与上述结果相同。
  • 使用QT::QueuedConnection。结果相同。

主要

#include <QApplication>
#include <QtGui>
#include <QTimer>
#include <QThread>

#include "mySplashScreen.h"
#include "myMainWindow.h"       // contains a configure function which takes a
                                // LONG time to load.

int main( int argc, char* argv[] )
{
    // estimate the load time
    int previousLoadTime_ms = 10000;

    QApplication a(argc, argv);
    MySplashScreen* splash = new MySplashScreen(QPixmap(":/splashScreen"));

    // progress timer. Has to be in a different thread since the
    // qApplication isn't started.
    QThread* timerThread = new QThread;

    QTimer* timer = new QTimer(0); // _not_ this!
    timer->setInterval(previousLoadTime_ms / 100.0);
    timer->moveToThread(timerThread);

    QObject::connect(timer, &QTimer::timeout, [&]
    {
        qApp->processEvents(); splash->incrementProgress(1); 
    });
    QObject::connect(timerThread, SIGNAL(started()), timer, SLOT(start()));
    timerThread->start();

    splash->show();
    a.processEvents();

    myMainWindow w;

    QTimer::singleShot(0, [&]
    {
        // This will be called as soon as the exec() loop starts.
        w.configure();  // this is a really slow initialization function
        w.show();
        splash->finish(&w);

        timerThread->quit();
    });

    return a.exec();
}

启动画面

#include <QSplashScreen>

class MySplashScreen : public QSplashScreen
{ 
    Q_OBJECT

public:

    MySplashScreen(const QPixmap& pixmap = QPixmap(), Qt::WindowFlags f = 0) 
        : QSplashScreen(pixmap, f)
    {
        m_pixmap = pixmap;
    }

    virtual void drawContents(QPainter *painter) override
    {
        QSplashScreen::drawContents(painter);

        // draw progress bar
    }

public slots:

    virtual void incrementProgress(int percentage)
    {
        m_progress += percentage;

        repaint();
    }

protected:

    int m_progress = 0;

private:

    QPixmap m_pixmap;
};

MyMainWindow

#include <QMainWindow>

class myMainWindow : public QMainWindow
{
public:

    void configure()
    {
        // Create and configure a bunch of widgets. 
        // This takes a long time.
    }
}

1 个答案:

答案 0 :(得分:1)

问题是因为设计是倒退的。 GUI线程不应该进行任何加载。 GUI线程的一般方法是:在GUI线程中不起作用。您应该生成一个工作线程来加载您需要加载的内容。它可以将事件(或使用排队连接调用插槽)发布到GUI线程及其启动屏幕。

当然,工作线程不应该创建任何GUI对象 - 它不能实例化从QWidget派生的任何内容。但是,它可以实例化其他内容,因此如果您需要任何昂贵的获取数据,请在工作线程中进行准备,然后在数据可用后在GUI线程中廉价地构建QWidget

如果延迟是由于库加载造成的,那么请明确加载工作线程中的所有库,并确保所有页面都驻留在内存中 - 例如在加载后读取整个.DLL它作为一个图书馆。

MyMainWindow::configure()可以在工作线程中调用,只要它不调用任何QWidget方法或构造函数。它可以做GUI工作,只是在屏幕上看不见。例如,您可以从磁盘加载QImage个实例,或者在QImage上进行绘制。

This answer提供了几种在不同线程GCD-style中执行仿函数的方法。

如果要构建构造成本高或构造其中许多窗口小部件的窗口小部件,则可以确保事件循环可以在每个窗口小部件的实例化之间运行。例如:

class MainWindow : public QMainWindow {
  Q_OBJECT
  QTimer m_configureTimer;
  int m_configureState = 0;
  Q_SLOT void configure() {
    switch (m_configureState ++) {
    case 0:
      // instantiate one widget from library A
      break;
    case 1:
      // instantiate one widget from library B
      ...
      break;
    case 2:
      // instantiate more widgets from A and B
      ...
      break;
    default:
      m_configureTimer.stop();
      break;
    }
  }
public:
  MainWindow(QWidget * parent = 0) : QMainWindow(parent) {
    connect(&m_configureTimer, SIGNAL(timeout()), SLOT(configure()));
    m_configureTimer.start(0);
    //...
  }
};