I have an application that has potentially long-running tasks and also possibly thousands or millions or results.
This specific application (code below) isn't of any worth, but it is aimed to provide a general use case of the need to maintain a responsive UI amid 'thousands' of results.
To be clear, I am aware that one should reduce the number of times the UI is polled. My question is regarding design principles that can be applied to this (and other similar) scenarios in keeping a responsive UI.
My first thought is to use a QTimer and process all 'results' every e.g. 200ms, an example which can be found here but needs adation.
What methods are available and which are preferred to keep a responsive UI?
A simple example of I am trying to explain is as follows. I have a UI that:
generates a list of integers,
passes it into a mapped function to pow(x,2) the value, and
measure the progress
When running this app, click the 'start' button will run the application, but due to the frequency of results being processed by the QueuedConnection: QFutureWatcher::resultReadyAt, the UI cannot respond to any user clicks, thus attempting to 'pause' or 'stop' (cancel) is futile.
Wrapper for QtConcurrent::mapped() function passing in lambda (for a member function)
#include <functional>
template <typename ResultType>
class MappedFutureWrapper
{
public:
using result_type = ResultType;
MappedFutureWrapper<ResultType>(){}
MappedFutureWrapper<ResultType>(std::function<ResultType (ResultType)> function): function(function){ }
MappedFutureWrapper& operator =(const MappedFutureWrapper &wrapper) {
function = wrapper.function;
return *this;
}
ResultType operator()(ResultType i) {
return function(i);
}
private:
std::function<ResultType(ResultType)> function;
};
MainWindow.h UI
class MainWindow : public QMainWindow {
Q_OBJECT
public:
struct IntStream {
int value;
};
MappedFutureWrapper<IntStream> wrapper;
QVector<IntStream> intList;
int count = 0;
int entries = 50000000;
MainWindow(QWidget* parent = nullptr);
static IntStream doubleValue(IntStream &i);
~MainWindow();
private:
Ui::MainWindow* ui;
QFutureWatcher<IntStream> futureWatcher;
QFuture<IntStream> future;
//...
}
MainWindow implementation
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
qDebug() << "Launching";
intList = QVector<IntStream>();
for (int i = 0; i < entries; i++) {
int localQrand = qrand();
IntStream s;
s.value = localQrand;
intList.append(s);
}
ui->progressBar->setValue(0);
}
MainWindow::IntStream MainWindow::doubleValue(MainWindow::IntStream &i)
{
i.value *= i.value;
return i;
}
void MainWindow::on_thread1Start_clicked()
{
qDebug() << "Starting";
// Create wrapper with member function
wrapper = MappedFutureWrapper<IntStream>([this](IntStream i){
return this->doubleValue(i);
});
// Process 'result', need to acquire manually
connect(&futureWatcher, &QFutureWatcher<IntStream>::resultReadyAt, [this](int index){
auto p = ((++count * 1.0) / entries * 1.0) * 100;
int progress = static_cast<int>(p);
if(this->ui->progressBar->value() != progress) {
qDebug() << "Progress = " << progress;
this->ui->progressBar->setValue(progress);
}
});
// On future finished
connect(&futureWatcher, &QFutureWatcher<IntStream>::finished, this, [](){
qDebug() << "done";
});
// Start mapped function
future = QtConcurrent::mapped(intList, wrapper);
futureWatcher.setFuture(future);
}
void MainWindow::on_thread1PauseResume_clicked()
{
future.togglePaused();
if(future.isPaused()) {
qDebug() << "Paused";
} else {
qDebug() << "Running";
}
}
void MainWindow::on_thread1Stop_clicked()
{
future.cancel();
qDebug() << "Canceled";
if(future.isFinished()){
qDebug() << "Finished";
} else {
qDebug() << "Not finished";
}
}
MainWindow::~MainWindow()
{
delete ui;
}
Explanation of why the UI is 'not responding'.
The UI loads w/o performing any action other than printing "Launching". When the method on_thread1Start_clicked() is invoked, it started the future, in addition to adding the following connection:
connect(&futureWatcher, &QFutureWatcher<IntStream>::resultReadyAt, [this](int index){
auto p = ((++count * 1.0) / entries * 1.0) * 100;
int progress = static_cast<int>(p);
if(this->ui->progressBar->value() != progress) {
qDebug() << "Progress = " << progress;
this->ui->progressBar->setValue(progress);
}
});
This connection listens for a result from the future, and acts upon it (this connect function runs on the UI thread). Since I am emulating a massive amount of 'ui updates', shown by int entries = 50000000;, each time a result is processed, the QFutureWatcher<IntStream>::resultReadyAt is invoked.
While this is running for +/- 2s, the UI does not respond to the 'pause' or 'stop' clicks linked to on_thread1PauseResume_clicked() and on_thread1Stop_clicked respectively.
Your approach of using QtConcurrent::mapped makes perfect sense, and I think that in theory it could be a good way of solving such a problem. The problem here is that the number of events that are added to the event queue are just too much to keep the UI responsive.
The reason why the UI is not responding is that you have only one event queue in the GUI thread. As a consequence your button clicked events are queued together with the resultReadyAt events. But the queue is just that, a queue, so if your button event enter the queue after say 30'000'000 of resultReadyAt event, it will be processed only when it comes its turn. The same holds for resize and move events. As a consequence the UI feels sluggish and not responsive.
One possibility would be to modify your mapping function so that instead of a single data point receives a chunk of the data. For example I'm splitting the 50'000'000 data in 1000 batch of 50'000 data. You can see that in this case the UI is responsive during all the execution. I have also added a 20ms delay in each function otherwise the execution is so fast that I cannot even press the stop/pause button.
There are also a couple of minor comments to your code:
In principle you don't need a wrapper class since you can pass the member function directly (again see my first example below). If you have problem maybe it's related to the Qt version or compiler that you are using.
You are actually changing the value you pass to doubleValue. That actually makes useless returning a value from the function.
#include <QApplication>
#include <QMainWindow>
#include <QProgressBar>
#include <QPushButton>
#include <QRandomGenerator>
#include <QtConcurrent>
#include <QVBoxLayout>
class Widget : public QWidget {
Q_OBJECT
public:
struct IntStream {
int value;
};
Widget(QWidget* parent = nullptr);
static QVector<IntStream> doubleValue(const QVector<IntStream>& v);
public slots:
void startThread();
void pauseResumeThread();
void stopThread();
private:
static constexpr int BATCH_SIZE {50000};
static constexpr int TOTAL_BATCHES {1000};
QFutureWatcher<QVector<IntStream>> m_futureWatcher;
QFuture<QVector<IntStream>> m_future;
QProgressBar m_progressBar;
QVector<QVector<IntStream>> m_intList;
int m_count {0};
};
Widget::Widget(QWidget* parent) : QWidget(parent)
{
auto layout {new QVBoxLayout {}};
auto pushButton_startThread {new QPushButton {"Start Thread"}};
layout->addWidget(pushButton_startThread);
connect(pushButton_startThread, &QPushButton::clicked,
this, &Widget::startThread);
auto pushButton_pauseResumeThread {new QPushButton {"Pause/Resume Thread"}};
layout->addWidget(pushButton_pauseResumeThread);
connect(pushButton_pauseResumeThread, &QPushButton::clicked,
this, &Widget::pauseResumeThread);
auto pushButton_stopThread {new QPushButton {"Stop Thread"}};
layout->addWidget(pushButton_stopThread);
connect(pushButton_stopThread, &QPushButton::clicked,
this, &Widget::stopThread);
layout->addWidget(&m_progressBar);
setLayout(layout);
qDebug() << "Launching";
for (auto i {0}; i < TOTAL_BATCHES; i++) {
QVector<IntStream> v;
for (auto j {0}; j < BATCH_SIZE; ++j)
v.append(IntStream {static_cast<int>(QRandomGenerator::global()->generate())});
m_intList.append(v);
}
}
QVector<Widget::IntStream> Widget::doubleValue(const QVector<IntStream>& v)
{
QThread::msleep(20);
QVector<IntStream> out;
for (const auto& x: v) {
out.append(IntStream {x.value * x.value});
}
return out;
}
void Widget::startThread()
{
if (m_future.isRunning())
return;
qDebug() << "Starting";
m_count = 0;
connect(&m_futureWatcher, &QFutureWatcher<IntStream>::resultReadyAt, [=](int){
auto progress {static_cast<int>(++m_count * 100.0 / TOTAL_BATCHES)};
if (m_progressBar.value() != progress && progress <= m_progressBar.maximum()) {
m_progressBar.setValue(progress);
}
});
connect(&m_futureWatcher, &QFutureWatcher<IntStream>::finished,
[](){
qDebug() << "Done";
});
m_future = QtConcurrent::mapped(m_intList, &Widget::doubleValue);
m_futureWatcher.setFuture(m_future);
}
void Widget::pauseResumeThread()
{
m_future.togglePaused();
if (m_future.isPaused())
qDebug() << "Paused";
else
qDebug() << "Running";
}
void Widget::stopThread()
{
m_future.cancel();
qDebug() << "Canceled";
if (m_future.isFinished())
qDebug() << "Finished";
else
qDebug() << "Not finished";
}
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
Another really good alternative could be using a separate working thread as suggested by Jeremy Friesner. If you want we can elaborate on that too =)
Related
I'm new to programming. Would like to know how to print the output data from the code below (C++) using the Qt. I need an answer to appear in the QTextEdit window.
for (int x=0; x<10; x++);
Here's how you might capture the qDebug messages in a QAbstractItemModel or QTextDocument. Both of those classes are models, their associated views are QListView (or any other view), and QTextEdit (preferably QPlainTextEdit or QTextBrowser), respectively.
// https://github.com/KubaO/stackoverflown/tree/master/questions/qdebug-window-output-52061269
#include <QtWidgets>
struct LogToModelData {
bool installed;
QtMessageHandler previous = {};
QList<QPointer<QObject>> models;
};
Q_GLOBAL_STATIC(LogToModelData, logToModelData)
void logToModelHandler(QtMsgType type, const QMessageLogContext &context,
const QString &msg) {
for (auto m : qAsConst(logToModelData->models)) {
if (auto model = qobject_cast<QAbstractItemModel *>(m)) {
auto row = model->rowCount();
model->insertRow(row);
model->setData(model->index(row, 0), msg);
} else if (auto doc = qobject_cast<QTextDocument *>(m)) {
QTextCursor cur(doc);
cur.movePosition(QTextCursor::End);
if (cur.position() != 0) cur.insertBlock();
cur.insertText(msg);
}
}
if (logToModelData->previous) logToModelData->previous(type, context, msg);
}
void logToModel(QObject *model) {
logToModelData->models.append(QPointer<QObject>(model));
if (!logToModelData->installed) {
logToModelData->previous = qInstallMessageHandler(logToModelHandler);
logToModelData->installed = true;
}
}
void rescrollToBottom(QAbstractScrollArea *view) {
static const char kViewAtBottom[] = "viewAtBottom";
auto *scrollBar = view->verticalScrollBar();
Q_ASSERT(scrollBar);
auto rescroller = [scrollBar]() mutable {
if (scrollBar->property(kViewAtBottom).isNull())
scrollBar->setProperty(kViewAtBottom, true);
auto const atBottom = scrollBar->property(kViewAtBottom).toBool();
if (atBottom) scrollBar->setValue(scrollBar->maximum());
};
QObject::connect(scrollBar, &QAbstractSlider::rangeChanged, view, rescroller,
Qt::QueuedConnection);
QObject::connect(scrollBar, &QAbstractSlider::valueChanged, view, [scrollBar] {
auto const atBottom = scrollBar->value() == scrollBar->maximum();
scrollBar->setProperty(kViewAtBottom, atBottom);
});
}
int main(int argc, char *argv[]) {
QApplication app{argc, argv};
QWidget ui;
QVBoxLayout layout{&ui};
QListView view;
QTextBrowser browser;
layout.addWidget(new QLabel(QLatin1String(view.metaObject()->className())));
layout.addWidget(&view);
layout.addWidget(new QLabel(QLatin1String(browser.metaObject()->className())));
layout.addWidget(&browser);
QStringListModel model;
view.setModel(&model);
logToModel(view.model());
logToModel(browser.document());
rescrollToBottom(&view);
rescrollToBottom(&browser);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [] {
static int n;
qDebug() << "Tick" << ++n;
});
timer.start(500);
ui.show();
return app.exec();
}
Also note that the separate declaration of the loop induction variable i is a very much obsolete C-ism - the last time you had to write code like that was two decades ago.
I am working on an application that takes an image and takes measurements and draws to the image. This is all done in a method that consists of several steps that involve user input from the mouse. This method is called using a pushbutton on the toolbar.
I am wondering if it is possible to return from this function before it is finished executing. It seems logical that if the pushbutton is pressed halfway through execution of these steps, it will "cancel" the first instance and start a new instance of these steps. Or to put it differently, how would one simply cancel execution of this method?
The one way that I can think to do this would be to use the signal to set a flag to and check that flag after each step is executed. If the flag == true, then return. It seems like there should be a better way to do this as opposed to several if statements.
It is possible to do this using threads, other issue is to stop it. You may check it before execution of each process step ...
So example implementation may look like this ? Worker executes steps ( timer simulates some action ) in its own thread. If start is invoked again, steps chain is replaced by new one. Other problem is what to do with current steps being replaced but this is another issue.
class Step
{
public:
virtual ~Step() {}
void setNextStep( Step* next )
{
successor = next;
}
Step* nextStep()
{
return successor;
}
virtual void process() = 0;
private:
Step* successor;
};
class Step1 : public Step
{
virtual void process()
{
std::cout << "Step1" << std::endl;
}
};
class Step2 : public Step
{
virtual void process()
{
std::cout << "Step2" << std::endl;
}
};
class Step3 : public Step
{
virtual void process()
{
std::cout << "Step3" << std::endl;
}
};
class Worker : public QObject
{
Q_OBJECT
public:
Worker( QObject* parent = 0 ) : QObject( parent ), timer( this )
{
QObject::connect( &timer, SIGNAL(timeout()), this, SLOT(work()));
}
public slots:
void start()
{
Step1* fStep = new Step1();
Step2* sStep = new Step2();
Step3* tStep = new Step3();
fStep->setNextStep( sStep );
sStep->setNextStep( tStep );
currentStep = fStep;
if( !timer.isActive() )
{
timer.start( 1 );
}
}
void work()
{
currentStep->process();
currentStep = currentStep->nextStep();
if( !currentStep )
{
std::cout << "finished";
timer.stop();
//test one run emit
emit quit();
}
}
void stop()
{
std::cout << "stop" << std::endl;
timer.stop();
emit quit();
}
signals:
void quit();
private:
QTimer timer;
Step* currentStep;
};
int main( int argc, char* argv[] )
{
QCoreApplication a( argc, argv );
Worker* worker = new Worker();
QThread* thread = new QThread();
worker->moveToThread( thread );
QObject::connect( thread, SIGNAL(started()), worker, SLOT(start()) );
QObject::connect( thread, SIGNAL(finished()), &a, SLOT(quit()) );
QObject::connect( worker, SIGNAL(quit()), thread, SLOT(quit()) );
QTimer::singleShot( 0, thread, SLOT(start(Priority)));
return a.exec();
}
BTW this poc is one great memory leak :)
I was reading Qt's Signals & Slots [1] and noticed that it claims signals and slots have much lower overhead than any new or delete operation. So I did a test:
#include <cmath>
#include <QtCore/QAtomicInt>
#include <QtCore/QCoreApplication>
#include <QtCore/QElapsedTimer>
#include <QtCore/QMetaObject>
#include <QtCore/QMetaMethod>
#include <QtCore/QObject>
#include <QtCore/QRunnable>
#include <QtCore/QTextStream>
#include <QtCore/QThread>
#include <QtCore/QThreadPool>
#include <QtCore/QTimer>
#include <QtCore/QVector>
using std::pow;
constexpr int const maxThreadCount(16);
constexpr int const maxIteration(100000);
constexpr int const maxPiDigit(1000);
void calcPi()
{
double sum(0);
for (int k(0); k < maxPiDigit; ++k) {
double a(4.0 / (k * 8 + 1));
double b(2.0 / (k * 8 + 4));
double c(1.0 / (k * 8 + 5));
double d(1.0 / (k * 8 + 6));
sum += pow(16, -k) * (a - b - c -d);
}
QTextStream out(stdout);
out << sum << endl;
}
class CalcPiWithQObject : public QObject
{
Q_OBJECT
public:
CalcPiWithQObject(QObject *parent = NULL);
public slots:
void start();
signals:
void finished();
}; // CalcPiWithQObject
CalcPiWithQObject::CalcPiWithQObject(QObject *parent):
QObject(parent)
{}
void CalcPiWithQObject::start()
{
calcPi();
finished();
}
class CalcPiWithQRunnable : public QRunnable
{
private:
static QAtomicInt count_;
public:
CalcPiWithQRunnable(QThreadPool *parent);
void run() override;
private:
QThreadPool *parent_;
}; // CalcPiWithQRunnable
QAtomicInt CalcPiWithQRunnable::count_(maxThreadCount);
CalcPiWithQRunnable::CalcPiWithQRunnable(QThreadPool *parent):
QRunnable(),
parent_(parent)
{
setAutoDelete(false);
}
void CalcPiWithQRunnable::run()
{
calcPi();
if (count_.fetchAndAddOrdered(1) < maxIteration) {
parent_->start(new CalcPiWithQRunnable(parent_));
}
delete this;
}
class PiTest : public QObject
{
Q_OBJECT
public:
PiTest(QObject *parent = NULL);
public slots:
void start();
void nextQObjectCall();
private:
QVector<QThread *> threads_;
QVector<CalcPiWithQObject *> calc_;
QThreadPool *threadPool_;
QElapsedTimer timer_;
int threadCount_;
int jobCount_;
}; // PiTest
PiTest::PiTest(QObject *parent):
QObject(parent),
threads_(maxThreadCount),
calc_(maxThreadCount),
threadPool_(new QThreadPool(this)),
threadCount_(maxThreadCount),
jobCount_(maxThreadCount)
{
threadPool_->setMaxThreadCount(maxThreadCount);
for (int i(0); i < maxThreadCount; ++i) {
threads_[i] = new QThread();
calc_[i] = new CalcPiWithQObject();
calc_[i]->moveToThread(threads_[i]);
QObject::connect(calc_[i], &CalcPiWithQObject::finished,
this, &PiTest::nextQObjectCall,
Qt::QueuedConnection);
QObject::connect(threads_[i], &QThread::started,
calc_[i], &CalcPiWithQObject::start,
Qt::QueuedConnection);
}
}
void PiTest::start()
{
timer_.start();
for (int i(0); i < maxThreadCount; ++i) {
threadPool_->start(new CalcPiWithQRunnable(threadPool_));
}
threadPool_->waitForDone();
int timePassed(timer_.elapsed());
QTextStream out(stdout);
out << "QThreadPool: " << timePassed << endl;
timer_.restart();
for (int i(0); i < maxThreadCount; ++i) {
threads_[i]->start();
}
}
static QMetaMethod nextCall(PiTest::staticMetaObject.method(PiTest::staticMetaObject.indexOfMethod("start")));
void PiTest::nextQObjectCall()
{
jobCount_++;
if (jobCount_ < maxIteration) {
nextCall.invoke(sender(), Qt::QueuedConnection);
QMetaObject::invokeMethod(sender(), "start",
Qt::QueuedConnection);
return;
}
threadCount_--;
if (threadCount_ == 0) {
for (int i(0); i < maxThreadCount; ++i) {
threads_[i]->quit();
}
int timePassed(timer_.elapsed());
QTextStream out(stdout);
out << "QThread: " << timePassed << endl;
qApp->quit();
}
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
PiTest *bench(new PiTest(qApp));
QTimer::singleShot(0, bench, SLOT(start()));
return qApp->exec();
}
#include "main_moc.cpp"
And I ran the test on an idle 20-core computer:
/usr/lib64/qt5/bin/moc -o main_moc.cpp main.cpp
clang++ -std=c++11 -fPIE -O2 -march=native -I/usr/include/qt5/ -L/usr/lib64/qt5 -lQt5Core -o bench main.cpp
./bench > test.out
grep QThread test.out
And here are the results:
QThreadPool: 4803
QThread: 9285
I tried different parameters, with longer pi calculation and less jobs, or vice versa, but the results were around the same. QThread+signal/slots was always lagging behind. With larger numbers of jobs, QThreadPool+new/delete can easily outperform QThread by up to 10-fold.
I feel somehow awkward about my benchmark code. Did I misunderstand something here? If signal/slot is faster than new/delete, what's the problem with my benchmark?
Thank you.
[1] http://doc.qt.io/qt-5/signalsandslots.html
There is a difference in signal performance depending on the connection type. When you create inter-thread connections, the connection is queued, and uses an event loop to schedule itself, and the event loop in Qt is not only fairly slow, but last time I checked it didn't provide any way to increase its update rate.
This makes signals across threads really slow, I've had cases where I had fine grained concurrency which suffered performance hit from multithreading rather than a performance boost.
Just to give you an idea of the difference between direct and queued connections:
#define COUNT 5000
class Ping : public QObject {
Q_OBJECT
Q_SIGNAL void pong(uint);
public slots: void ping(uint c) { if (c < COUNT) emit pong(++c); else qDebug() << t.nsecsElapsed(); }
};
//...
QObject::connect(&p1, SIGNAL(pong(uint)), &p2, SLOT(ping(uint)), Qt::DirectConnection);
QObject::connect(&p2, SIGNAL(pong(uint)), &p1, SLOT(ping(uint)), Qt::DirectConnection);
//...
p1.ping(0);
Results:
Direct connection (in same thread) - 570504 nsec
Queued connection (in same thread) - 29670333 nsec
Queued connection (different threads) - 53343054 nsec
As you can plainly see, an inter-thread connection is almost 100 times slower than a direct one. And I suspect that documentation you linked to refers to a direct connection.
All in all, I'd say your test is a total mess. You should really streamline it, make it simple and focus on the issue you raise.
Lastly, direct connections might be faster than new/delete, but queued connections most certainly are not, they are much slower, and definitely the key factor behind the varying performance. The claim made in the documentation you linked to has absolutely nothing to do with QThread + worker vs QRunnable + thread pool performance. And finally, in both cases you use both dynamic memory allocation/deallocation and queued connections.
Sorry for basic question. I'm trying to show json in QPlainTextWidget. I have api function which have console output and contains all needed data. Looks like that:
int iperf_run_server(struct iperf_test *test)
{
int result, s, streams_accepted;
fd_set read_set, write_set;
struct iperf_stream *sp;
struct timeval now;
struct timeval* timeout;
......
if (test->json_output)
if (iperf_json_start(test) < 0)
return -1;
if (test->json_output) {
cJSON_AddItemToObject(test->json_start, "version", cJSON_CreateString(version));
cJSON_AddItemToObject(test->json_start, "system_info", cJSON_CreateString(get_system_info()));
} else if (test->verbose) {
iprintf(test, "%s\n", version);
iprintf(test, "%s", "");
fflush(stdout);
printf("%s\n", get_system_info());
}
.....
cleanup_server(test);
if (test->json_output) {
if (iperf_json_finish(test) < 0)
return -1;
}
....
return 0;
}
For now I have first thread with my gui, and second thread, contains class which run this function on a signal. All things works normally, but i'm not fully understand, how I can "stop" iperf_run_server for "reading/buffering" output, without any changes in api.
The simplest thing to do would be to collect each message in a string, and emit a signal from the object running in the second thread. You can connect that signal to a slot in an object in the GUI thread.A zero-timeout timer is invoked each time the event loop is done processing other events - it is a useful mechanism to leverage to run things "continuously".
For example:
#include <QApplication>
#include <QPlainTextEdit>
#include <QThread>
#include <QBasicTimer>
#include <QTextStream>
//! A thread that's always safe to destruct.
class Thread : public QThread {
private:
// This is a final class.
using QThread::run;
public:
Thread(QObject * parent = 0) : QThread(parent) {}
~Thread() {
quit();
wait();
}
};
class IperfTester : public QObject {
Q_OBJECT
struct Test { int n; Test(int n_) : n(n_) {} };
QList<Test> m_tests;
QBasicTimer m_timer;
public:
IperfTester(QObject * parent = 0) : QObject(parent) {
for (int i = 0; i < 50; ++i) m_tests << Test(i+1);
}
//! Run the tests. This function is thread-safe.
Q_SLOT void runTests() {
QMetaObject::invokeMethod(this, "runTestsImpl");
}
Q_SIGNAL void message(const QString &);
private:
Q_INVOKABLE void runTestsImpl() {
m_timer.start(0, this);
}
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != m_timer.timerId()) return;
if (m_tests.isEmpty()) {
m_timer.stop();
return;
}
runTest(m_tests.first());
m_tests.removeFirst();
}
void runTest(Test & test) {
// do the work
QString msg;
QTextStream s(&msg);
s << "Version:" << "3.11" << "\n";
s << "Number:" << test.n << "\n";
emit message(msg);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPlainTextEdit log;
// This order is important: the thread must be defined after the object
// to be moved into the thread.
IperfTester tester;
Thread thread;
tester.moveToThread(&thread);
thread.start();
log.connect(&tester, SIGNAL(message(QString)), SLOT(appendPlainText(QString)));
log.show();
tester.runTests();
return a.exec();
// Here, the thread is stopped and destructed first, following by a now threadless
// tester. It would be an error if the tester object was destructed while its
// thread existed (even if it was stopped!).
}
#include "main.moc"
This problem is bothering me because it should work, but sadly it does not.
What i try to achieve is to read the standard output of a certain process and make another process handle it i.e. print it out.
The process that produces output looks like this:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
int main() {
for (int i = 0; i < 100; i++) {
printf("yes %d\n",i);
fflush(stdout);
sleep(1);
}
return 0;
}
The process is started in another application like this:
#include <QProcess>
...
QProcess * process = new QProcess;
SomeClass * someClass = new SomeClass(process);
connect(process,SIGNAL(readyRead()),someClass,SLOT(onReadyRead()));
process->start("../Test/Test",QStringList());
if (!process->waitForStarted(4000)) {
qDebug() << "Process did not start.";
}
...
void SomeClass::onReadyRead() {
qDebug() << "Reading:" << process->readAllStdOutput();
}
My expected output would be:
Reading: yes 0
Reading: yes 1
...
Reading: yes 99
However i get no output at all.
And when i use QCoreApplication i get all the output but not through the signal/slot but directly in the console.
I dont understand because it works in another application that uses Qt 4.8.
My question is, is anyone experiencing the same problem or does anyone know how i can get the expected behaviour?
Your problem in the answer you provide lies in misunderstanding how the reading works. It simply returns whatever data you've got there, whether there are line endings or not. By spawning a thread and sleeping between lines, you're effectively sending the inter-process data in line-sized chunks since the pipe is flushed when you wait long enough.
So, your answer, while working, is not really how one should do it. You need to use readLine() to chop the incoming data into lines. Below is an example with following qualities:
There's just one executable :)
Only Qt apis are used. This reduces the runtime memory consumption.
Both processes cleanly terminate.
The amount of code is as minimal as practicable.
// https://github.com/KubaO/stackoverflown/tree/master/questions/process-17856897
#include <QtCore>
QTextStream out{stdout};
class Slave : public QObject {
QBasicTimer m_timer;
int m_iter = 0;
void timerEvent(QTimerEvent * ev) override {
if (ev->timerId() == m_timer.timerId()) {
out << "iteration " << m_iter++ << endl;
if (m_iter > 35) qApp->quit();
}
}
public:
Slave(QObject *parent = nullptr) : QObject(parent) {
m_timer.start(100, this);
}
};
class Master : public QObject {
Q_OBJECT
QProcess m_proc{this};
Q_SLOT void read() {
while (m_proc.canReadLine()) {
out << "read: " << m_proc.readLine();
out.flush(); // endl implicitly flushes, so we must do the same
}
}
Q_SLOT void started() {
out << "started" << endl;
}
Q_SLOT void finished() {
out << "finished" << endl;
qApp->quit();
}
public:
Master(QObject *parent = nullptr) : QObject(parent) {
connect(&m_proc, SIGNAL(readyRead()), SLOT(read()));
connect(&m_proc, SIGNAL(started()), SLOT(started()));
connect(&m_proc, SIGNAL(finished(int)), SLOT(finished()));
m_proc.start(qApp->applicationFilePath(), {"dummy"});
}
};
int main(int argc, char *argv[])
{
QCoreApplication app{argc, argv};
if (app.arguments().length() > 1)
new Slave{&app}; // called with an argument, this is the slave process
else
new Master{&app}; // no arguments, this is the master
return app.exec();
}
#include "main.moc"
Based on the code you've posted, you're connecting to the class slot with this: -
connect(process,SIGNAL(readyRead()),someClass,SLOT(onReadyReadStdOutput()));
But the function in the class is declared like this: -
void SomeClass::onReadyRead();
If you're expecting onReadyRead to be called, then you should be calling it in the SLOT, rather than onReadyReadStdOutput. So change your connection to: -
connect(process,SIGNAL(readyRead()),someClass,SLOT(onReadyRead()));
Well i solved my problem.
If the process is started with startDetached() it will not receive the signals from readyRead(), readyReadStandardOutput() and readyReadStandardError().
So just starting it with start() solved the problem.
However i noticed that if i start and do the while loop and prints in main() it will read everything at once even if it ends with \n. So i started the while loop in a thread and that problem was also solved. Everything prints as expected.
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
explicit Thread(QObject *parent = 0) : QThread(parent) {}
protected:
void run() {
for (int i = 0; i < 100; i++) {
std::cout << "yes" << i << std::endl;
msleep(200);
}
exit(0);
}
};
int main(int argc, char ** argv) {
QCoreApplication app(argc,argv);
Thread * t = new Thread();
t->start();
return app.exec();
}
TestP main.cpp
#include <QProcess>
#include <iostream>
class Controller : public QObject
{
Q_OBJECT
private:
QProcess * process;
public:
Controller(QObject *parent = 0) :
QObject(parent), process(new QProcess) {}
void init(const QString &program) {
connect(process,SIGNAL(readyRead()),this,SLOT(readStdOut()));
connect(process,SIGNAL(started()),this,SLOT(onStarted()));
connect(process,SIGNAL(finished(int)),this,SLOT(onFinished(int)));
process->start(program);
}
private slots:
void readStdOut() {
std::cout << "YES " << QString(process->readAllStandardOutput()).toUtf8().constData() << std::endl;
}
void onStarted(){
std::cout << "Process started" << std::endl;
}
void onFinished(int) {
std::cout << "Process finished: " << signal << std::endl;
}
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
Controller c;
c.init("../Test/Test");
return a.exec();
}