gpt4 book ai didi

c++ - 从线程重绘启动画面/禁用断言

转载 作者:行者123 更新时间:2023-11-30 05:46:27 26 4
gpt4 key购买 nike

问题

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

根据需要,这发生在 QCoreApplicationexec 调用之前。我已经让它工作(仅在 Release模式下)在 X11 和 Windows 上,方法是将计时器放在第二个线程中,并在启动画面中调用一个函数来更新进度并重新绘制小部件。但是,这在 Debug模式下不起作用,因为它会产生以下错误:

“QCoreApplication::sendEvent 中的 ASSERT 失败:“无法将事件发送到不同线程拥有的对象。”

我并不真的担心这个断言,因为代码在发布时不会崩溃,它只是一个启动画面,但是我需要能够在调试中运行程序,所以我想要么 a) 重构代码,这样它就不会触发断言,或者 b) 取消这个特定的断言。

尝试过:

  • 使用 update() 而不是 repaint()。这不会导致断言,但它也不会重新绘制,因为主线程太忙于加载共享库等,并且在我准备好调用 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;
};

我的主窗口

#include <QMainWindow>

class myMainWindow : public QMainWindow
{
public:

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

最佳答案

问题是因为设计落后了。 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);
//...
}
};

关于c++ - 从线程重绘启动画面/禁用断言,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28905258/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com