Qt:如何在 QTimer 处于活动状态时延迟程序?
Posted
技术标签:
【中文标题】Qt:如何在 QTimer 处于活动状态时延迟程序?【英文标题】:Qt: How to delay a program while QTimer is active? 【发布时间】:2011-01-28 23:39:01 【问题描述】:请参考以下链接,因为我试图简化我遇到的问题,但现在遇到了我无法解决的问题。
链接:Qt: How to use QTimer to print a message to a QTextBrowser every 10 seconds?
在上面链接的帖子中,我通过简单地说我想按一个按钮并让它每 10 秒在QTextBrowser
中显示一些内容来简化我正在尝试执行的任务。当时我无法让QTimer
工作,所以我想如果我能让QTimer
工作,那么我就可以完成我的任务。
我真正想做的是从文件中读取行,每 2500 行后我想打印一条消息,然后等待 10 秒。
伪代码:
while(not at the end of the file)
read in 2500 lines
print a message
wait 10 seconds
QTimer
很好,但它与我想要它做的相反。他们不是发送消息并等待 10 秒,而是先等待 10 秒,超时然后发送消息。
所以为了让它按我想要的方式工作,我首先调用了printMessage()
SLOT,然后我创建了一个名为stopTimer()
的SLOT,它只是停止了计时器。因此,在 10 秒过去后,它会简单地调用 stopTimer(),然后继续处理输入文件。
真正的问题:
Qt 在继续执行代码之前不会等待QTimer
完成。我希望代码在读取接下来的 2500 行代码之前等待整整 10 秒。我发现QTimer
有一个isActive()
函数,它返回一个布尔值。
所以在我希望 10 秒延迟完成的地方,我输入了以下内容:
while(timer->isActive());
我认为程序会在这个循环中停留 10 秒,然后在 QTimer
超时后退出,因为条件会为假。问题是它不会退出循环,因为无论它在这个循环中等待多长时间,计时器的状态都不会改变!我使用调试器进行了检查,isActive( )
无论经过多少时间都保持为真。
然后我省略了while(timer->isActive())
循环并观察了调试器中的计时器。似乎计时器直到它退出while(而不是在文件末尾)才真正开始计时。所以我相信,由于while(timer->isActive())
循环嵌套在其中,它导致它永远不会超时。我可能是错的,但这似乎正在发生。此外,令人讨厌的是QTimer
对象没有显示计时器在活动时经过的时间的字段。因此,我根本无法检查经过的时间来进一步调试。
请有人对此进行测试或让我知道解决方法!
对于听起来很简单的事情,这是我最近遇到的最大痛苦,但我一般不使用 Qt,所以可能是我缺乏经验。
这是我目前冻结的代码的摘录:
void Form::startBtn_pushed()
QTimer *timer = new QTimer(this);
QFile file(“file.txt”);
QTextStream stream(&file);
QString line;
int lineCount = 0;
connect(timer, SIGNAL(timeout()), this, SLOT(stopTimer()));
while(!(stream.atEnd())
line = stream.readLine();
lineCount++;
if(lineCount == 2500)
printMessage();
timer->start(10000);
while(timer->isActive()); //Wait for timer to timeout before continuing
很抱歉,这篇文章很长,如果我在这里的代码中出现了任何语法错误。我的开发机器上没有互联网访问权限,所以我不得不在这里重新输入。
【问题讨论】:
结帐 qtimer::singleshot(抱歉太忙,无法完整回答) 【参考方案1】:像while(timer.isActive())
这样的操作根本不是一个好主意,因为它会导致您的应用程序消耗大约 100% 的 CPU 时间。它还将导致您的应用程序永远不会返回到执行计时器实际代码的事件处理循环,这就是它冻结的原因。
如果你仍然想使用这种方法,你应该在循环中调用 QCoreApplication::processEvents()。它将暂时将控制权交还给事件循环,因此会导致计时器超时。您可以在启动之前调用 timer.setSingleShot(true) 而不是将 timeout() 连接到 stopTimer(),它会导致它在第一次超时后自动停止。
请注意,当您在每次按下按钮时创建一个新计时器时,您会出现内存泄漏。他们肯定是你的形式的孩子并且会被销毁,但只有当形式被销毁时。
如果您想要更优雅的方法,您可以创建一个单独的类来读取该文件。在构造函数中,您将打开文件和流,它们应该是此类中的字段。这个类还应该有一个 readMore() 插槽,它将读取 2500 行,然后输入一条消息并返回。如果它没有到达流的末尾,那么它将调用 QTimer::singleShot(10000, this, SLOT(readMore())),这将导致事件循环在 10 秒内再次调用 readMore()。代码看起来像这样(没有检查错误):
// myfilereader.h
class Form;
class MyFileReader: public QObject
Q_OBJECT
public:
MyFileReader(const QString &fileName);
// this should be called after you create an instance of MyFileReader
void startReading() readMore();
private:
QFile file;
QTextStream stream;
private slots:
void readMore();
signals:
void message(); // this should be connected to printMessage() in the Form
void finished();
;
// myfilereader.cpp
MyFileReader::MyFileReader(const QString &fileName):
file(fileName),
stream(&file),
// open the file, possibly throwing an exception
// or setting some sort of "invalid" flag on failure
void MyFileReader::readMore()
QString line;
int lineCount = 0;
while(!(stream.atEnd())
line = stream.readLine();
lineCount++;
if(lineCount == 2500)
emit message();
break;
if (stream.atEnd())
emit finished();
else
QTimer::singleShot(10000, this, SLOT(readMore()));
这是一种更重量级的方法,但这是异步事件处理的代价。您也可以将所有这些东西放入 Form 类中,但我认为使用单独的类更好。
正如丹尼尔指出的那样,如果读取 2500 行需要很长时间,比如 5 秒,则消息将在读取完成后 10 秒后打印,即开始后 15 秒。如果您希望无论读取需要多长时间,大约每 10 秒打印一次消息,您应该在类中添加一个 QTimer timer
字段,将它的 timeout() 信号连接到 MyFileReader 构造函数中的 readMore() 插槽,然后在startReading() 方法在调用 readMore() 之前调用 timer.start()。现在,在 readMore() 的末尾执行以下操作:
if (stream.atEnd())
timer.stop();
emit finished();
在这种情况下,您需要一个 QTimer 字段,因为您无法取消 QTimer::singleShot() 调用,但如果您已到达流的末尾,则需要这样做,否则您的 readMore() 将保持一次又一次地接到电话,即使没有什么要读的了。请注意,即使在这种情况下,如果读取 2500 行所需的时间超过这 10 秒,消息出现的频率仍然可能低于每 10 秒出现一次。如果你想要正好 10 秒,你可能应该检查循环中的经过时间,但我认为这有点过头了,除非你预计阅读速度会很慢。
有点跑题了,但是如果你想要一个简单的方法来避免内存泄漏,你也可以在构造函数中这样做:
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
当您emit finished()
时,它会自动将您的阅读器标记为删除,一旦发生这种情况,一旦控件返回事件循环,阅读器就会被删除。也就是说,在所有连接到finished() 信号的槽返回之后。这种方法允许您只在堆上分配一个 MyFileReader,然后丢弃指针而不用担心内存泄漏。
【讨论】:
感谢您抽出宝贵时间为我解决此问题。这个想法听起来很棒,但最终这让我有点难以实施。请参考我的解决方案,让我知道您的想法。再次感谢! @Aaron,你到底有什么不清楚的地方?我可以编辑我的答案来澄清。【参考方案2】:QTimer 有一个称为timedout() 的信号。如果将插槽连接到此,则可以将初始计时器设置为非常短的间隔(可能为 1 MS)。当计时器到期时,您可以在插槽内发送消息。在 slot 结束时,将时间间隔设置为 10 秒,将 singleshot 设置为 false,然后再次启动计时器。
如果您不想在每次调用插槽时都进行设置,您可以简单地设置 2 个插槽。第一次,插槽为其余的调用进行设置。它还会断开自己与 QTimer 的连接并连接第二个插槽。然后事情继续愉快地进行。
编辑:
另外,请注意,当调用槽时,它们会在事件线程上调用。因此,通过单击一个按钮并放置一个自旋阻塞/忙碌等待直到计时器到期的循环,您可以保证您不会进入此状态,因为您正在阻塞将处理超时信号的线程。
【讨论】:
您甚至可以将超时设置为 0。这明确记录为“控件返回事件循环后立即超时”。我经常使用它来异步调用插槽。它甚至有一个非常有用的静态实用方法 singleShot(),所以我不必创建 QTimer 实例。【参考方案3】:QTimer 在 Qt 的事件循环中完成所有处理,因此您的代码必须返回事件循环以使计时器超时。所以你想要做的是让startBtn_pushed
设置计时器,将一个插槽连接到计时器的timeout
信号,然后可能会调用该插槽本身(以便立即调用它)。它看起来像这样:
// timer, file, and stream are now instance variables (or maybe file and stream
// are broken out into their own class... up to you.
void Form::startBtn_pushed()
// timer has been allocated before (but not started)
file.open(“file.txt”);
stream.setDevice(&file);
connect(timer, SIGNAL(timeout()), this, SLOT(readAndPrint()));
timer->start(10000);
readAndPrint(); // respond to the button press immediately
void Form::readAndPrint()
int lineCount = 0;
while(!(stream.atEnd() && lineCount < 2500)
line = stream.readLine();
lineCount++;
if(lineCount == 2500)
printMessage();
【讨论】:
如果我采用“将所有内容放入表单”方法,这看起来就像我的解决方案。但是你真的应该在读完 2500 行之后(除非你已经到达 EOF),让计时器单次触发并从 readAndPrint() 调用 timer.start()。否则,如果读取 2500 行需要足够长的时间(例如,通过慢速网络读取文件时),您最终可能会遇到不合时宜的行为。 在readAndPrint
需要很长时间的情况下,运行计时器可能正是 OP 想要的; readAndPrint
将每 10 秒调用一次,除非它花费的时间超过 10 秒,在这种情况下,它将在再次进入事件循环时立即调用。如果你在readAndPrint
结束时设置了一个10秒的定时器,保证调用之间的时间超过10秒。如果第一个呼叫需要 8 秒,则第二个呼叫将在第一个呼叫开始后 18 秒开始。两种方法都有其优点。取决于 OP 想要哪种行为。
好点。最有可能的是,它打算每 10 秒打印一条消息。起初我的想法不同,因为 OP 发布的代码试图在读取 2500 行后创建 10 秒延迟,但这可能是一个错误。【参考方案4】:
我想出了一个比这里提出的更简单的解决方案。
我意识到使用 QTime 对象而不是 QTimer 对象要容易得多。
基本上,您启动一个 QTime 对象,然后使用它的 elapsed() 函数来检查自启动以来经过了多少时间。
感谢大家抽出时间提供帮助。我确实尝试在我的代码中实现你的几个解决方案,但遇到了一些麻烦,因为我不是 Qt 的专业人士,而且它们比我想象的要复杂。最后,我发现这个解决方案是一个简单的解决方案,本来应该是一个简单的问题。
我当然从这个问题中学到了很多,我希望你们也都这样做了。
我的问题是为什么没有人从一开始就建议使用 QTime?
再次感谢!
我的解决方案:
void Form::startBtn_pushed()
QTime *timer = new QTime();
QFile file(“file.txt”);
QTextStream stream(&file);
QString line;
int lineCount = 0;
connect(timer, SIGNAL(timeout()), this, SLOT(stopTimer()));
while(!(stream.atEnd())
line = stream.readLine();
lineCount++;
if(lineCount == 2500)
timer->start();
while(1)
QApplication::processEvents();
if(timer->elapsed() == 10000)
printMessage();
break;
【讨论】:
这与您之前的解决方案存在相同的问题:螺纹旋转。尝试一下(在将其更改为elapsed() >= 10000
后,它可能会经常失败),您会看到大约 100% 的 CPU 时间使用率,因为 QApplication::processEvents() 几乎会立即返回,因为它可能什么都没有来处理。为了使您的应用程序空闲,您必须将控制权交还给事件循环。或者,在最坏的情况下,您可以使用QMutex mutex; mutex.lock(); QWaitCondition().wait(&mutex, 10000); mutex.unlock();
,但这会使您的应用程序无响应。【参考方案5】:
QEventLoop l;
connect( timer, SIGNAL( timeout() ), &l, SLOT( quit() ) );
l.exec(); // Waiting without freezing ui
【讨论】:
以上是关于Qt:如何在 QTimer 处于活动状态时延迟程序?的主要内容,如果未能解决你的问题,请参考以下文章
FCM 点击如何在应用程序处于后台状态时打开用户指定的活动而不是默认活动
如何在应用程序处于非活动状态时获取推送通知ios swift3
当应用程序从后台处于活动状态时,如何避免在本机反应中安装组件?