Blocking a Qt application during downloading a short file - qt

I'm writing an application using Qt4.
I need to download a very short text file from a given http address.
The file is short and is needed for my app to be able to continue, so I would like to make sure the download is blocking (or will timeout after a few seconds if the file in not found/not available).
I wanted to use QHttp::get(), but this is a non-blocking method.
I thought I could use a thread : my app would start it, and wait for it to finish. The thread would handle the download and quit when the file is downloaded or after a timeout.
But I cannot make it work :
class JSHttpGetterThread : public QThread
{
Q_OBJECT
public:
JSHttpGetterThread(QObject* pParent = NULL);
~JSHttpGetterThread();
virtual void run()
{
m_pHttp = new QHttp(this);
connect(m_pHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool)));
m_pHttp->setHost("127.0.0.1");
m_pHttp->get("Foo.txt", &m_GetBuffer);
exec();
}
const QString& getDownloadedFileContent() const
{
return m_DownloadedFileContent;
}
private:
QHttp* m_pHttp;
QBuffer m_GetBuffer;
QString m_DownloadedFileContent;
private slots:
void onRequestFinished(int Id, bool Error)
{
m_DownloadedFileContent = "";
m_DownloadedFileContent.append(m_GetBuffer.buffer());
}
};
In the method creating the thread to initiate the download, here is what I'm doing :
JSHttpGetterThread* pGetter = new JSHttpGetterThread(this);
pGetter->start();
pGetter->wait();
But that doesn't work and my app keeps waiting. It looks lit the slot 'onRequestFinished' is never called.
Any idea ?
Is there a better way to do what I'm trying to do ?

Instead of using a thread you can just go into a loop which calls processEvents:
while (notFinished) {
qApp->processEvents(QEventLoop::WaitForMore | QEventLoop::ExcludeUserInput);
}
Where notFinished is a flag which can be set from the onRequestFinished slot.
The ExcludeUserInput will ensure that GUI related events are ignored while waiting.

A little late but:
Do not use these wait loops, the correct way is to use the done() signal from QHttp.
The requestFinished signal from what I have seen is just for when your application has finished the request, the data may still be on its way down.
You do not need a new thread, just setup the qhttp:
httpGetFile= new QHttp();
connect(httpGetFile, SIGNAL(done(bool)), this, SLOT(processHttpGetFile(bool)));
Also do not forget to flush the file in processHttpGetFile as it might not all be on the disk.

you have to call QThread::quit() or exit() if you are done - otherwise your thread will run forever...

I chose to implement David's solution, which seemed to be the easiest.
However, I had handle a few more things :
I had to adapt the QEventLoop enum values for Qt4.3.3 (the version I'm using);
I had to track the request Id, to make sure to exit the while loop when the download request is finished, and not when another request is finished;
I added a timeout, to make sure to exit the while loop if there is any problem.
Here is the result as (more or less) pseudo-code :
class BlockingDownloader : public QObject
{
Q_OBJECT
public:
BlockingDownloaderBlockingDownloader()
{
m_pHttp = new QHttp(this);
connect(m_pHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool)));
}
~BlockingDownloader()
{
delete m_pHttp;
}
QString getFileContent()
{
m_pHttp->setHost("www.xxx.com");
m_DownloadId = m_pHttp->get("/myfile.txt", &m_GetBuffer);
QTimer::singleShot(m_TimeOutTime, this, SLOT(onTimeOut()));
while (!m_FileIsDownloaded)
{
qApp->processEvents(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents);
}
return m_DownloadedFileContent;
}
private slots:
void BlockingDownloader::onRequestFinished(int Id, bool Error)
{
if (Id == m_DownloadId)
{
m_DownloadedFileContent = "";
m_DownloadedFileContent.append(m_GetBuffer.buffer());
m_FileIsDownloaded = true;
}
}
void BlockingDownloader::onTimeOut()
{
m_FileIsDownloaded = true;
}
private:
QHttp* m_pHttp;
bool m_FileIsDownloaded;
QBuffer m_GetBuffer;
QString m_DownloadedFileContent;
int m_DownloadId;
};

I used QNetworkAccsessManager for same necessity. Because this class managing connections RFC base (6 proccess same time) and non-blocking.
http://qt-project.org/doc/qt-4.8/qnetworkaccessmanager.html

How about giving the GUI some amount of time to wait on the thread and then give up.
Something like:
JSHttpGetterThread* pGetter = new JSHttpGetterThread(this);
pGetter->start();
pGetter->wait(10000); //give the thread 10 seconds to download
Or...
Why does the GUI thread have to wait for the "downloader thread" at all? When the app fires up create the downloader thread, connect the finished() signal to some other object, start the downloader thread, and return. When the thread has finished, it will signal the other object which can resume your process.

Related

Signals emitted from a QThread worker class do not arrive

I have this simplified code:
class MyCustomObject {
};
class DeviceConnection : public QObject {
Q_OBJECT
public:
explicit DeviceConnection(QObject* const parent = nullptr);
signals:
void readFinished(MyCustomObject result);
public slots:
void readFromDevice();
};
DeviceConnection::readFromDevice() {
/* ... */
emit readFinished(MyCustomObject());
}
void MainWindow::on_actionRead_triggered() {
QThread* const thread = new QThread(this);
DeviceConnection* const connection = new DeviceConnection();
connection->moveToThread(thread);
thread->start();
connect(connection, &DeviceConnection::readFinished, this, [=](MyCustomObject data) {
/* This never runs. */
connection->deleteLater();
thread->quit();
});
QTimer::singleShot(0, connection, &DeviceConnection::readFromDevice);
}
This starts reading just fine. I can see in the debugger that I am getting to the emit line, and I am getting there in the thread. But I can also see in the debugger, and in the behavior of the code, that the readFinished lambda is never called. This is also true with slots that aren't lambdas. What's the problem?
Edit: This code runs fine when I don't use an extra thread, but of course it blocks the main thread while readFromDevice() runs.
I figured it out. Unfortunately I simplified the important bit away when I first asked the question, but I just edited it back in.
The problem is that MyCustomObject cannot be enqueued in the Qt message queue. To do that, you need to run this:
qRegisterMetaType<MyCustomObject>("MyCustomObject");
or
// ideally just after the definition for MyCustomObject
Q_DECLARE_METATYPE(MyCustomObject);
// any time before you want to enqueue one of these objects
qRegisterMetaType<MyCustomObject>();
your defined signal should take an argument of QString type.

breaking a loop called inside the slot of a QObject living in a thread

AcquisitionManager is a QObject living in a thread, I use it to acquire samples from an acquisition PCI-Express card :
m_acquisitionThread = new QThread(this);
m_acquisitionManager = new AcquisitionManager();
m_acquisitionManager->moveToThread(m_acquisitionThread);
m_acquisitionThread->start();
In my application code (living in the main thread), I use this :
QMetaObject::invokeMethod(m_acquisitionManager, "executeDataAcquisition", Qt::QueuedConnection);
to launch data acquisitions.
Inside 'executeDataAcquisition' slot, I have a loop :
for (size_t i = 0; i < numberOfLoops; ++i)
{
// blocking calls...
}
Sometimes, I want the user to abort acquisitions ASAP, I thought about using a boolean variable (+ volatile) that I modify from the main thread. Even if this isn't very thread safe, the boolean variable is only read by the 'slave' thread and only written by the master thread (main) :
for (size_t i = 0; i < numberOfLoops && m_bKeepAcquiring; ++i)
{
// blocking calls...
}
Is it right to do so ? I saw this technique in a lot of software I worked on but I don't know if it's safe to do so. Is there another technique ?
Well with "even if this isn't very thread safe" this becomes a bit of a code style question really. I mean if you don't care about direct cross-thread access then I guess it doesn't matter.
Personally I probably would not do it that way. It's pretty simple to add a method to the worker thread to set the "stop" flag (and even protect the flag with a simple mutex like QReadWriteLock). Then the stop can be ordered "properly" with a signal or queued meta method invocation. If the flag isn't likely to be set from anywhere except a main controller thread, skipping a mutex might be fine, though it's also fairly cheap insurance.
class AcquisitionManager ... {
public slots:
void requestStop() {
QWriteLocker lock(&m_flagMutex);
m_bStopRequested = true;
}
void executeDataAcquisition() {
for (size_t i = 0; i < numberOfLoops && !stopRequested(); ++i) {
// blocking calls...
QCoreApplication::processEvents(); // gives a chance for queued requestStop() call to be invoked
}
}
private:
inline bool stopRequested() {
QReadLocker lock(&m_flagMutex);
return m_bStopRequested;
}
bool m_bStopRequested = false;
QReadWriteLock m_flagMutex;
};

Close previous slot if still running

I got a searchfield that triggers a database search.
connect(searchField,SIGNAL(textChanged(QString)),this,SLOT(searchSong(QString)));
But the database search takes longer then typing the next search condition.
SLOTS are queued but I don't want this. When a new signal is send the previous SLOT needs to be cancelled and restarted.
How can I do this?
You are going the wrong way. What you have to do is create a flag that indicates if there is a pending request, and if there is when searchSong is called then cancel it and launch a new request:
*.h
private:
bool is_busy;
*.cpp
// constructor
FooClass::FooClass(foo_paramenters):
BaseClass(args), is_busy(false)
{
// some code
connect(searchField, &QLineEdit::textChanged, this, &FooClass::searchSong);
}
void FooClass::searchSong(const QString & text){
if(is_busy)
cancel_request();
is_busy = true;
send_request(text);
}
void FooClass::receive_answer_from_request(){
// some code
is_busy = false;
}

QTimer timeout and QMutex interaction

Let's say we have some basic timer and a slot which is invoked periodically.
SomeObject::SomeObject()
{
QTimer *timer = new QTimer;
connect(timer , SIGNAL(timeout()), this, SLOT(updateState()));
}
void SomeObject::updateState()
{
// some operations leading to update of updatedValue
}
I also have function from same object which forces updates and returns some value.
bool SomeObject::getLatestState()
{
updateState();
return updatedValue;
}
This function may be directly called from different threads. This brings question of thread safety to mind. Simple mutex lock in getLatestState will not help as in some rare cases getLatestState is called from another thread that starts updateState. And at the same time timer's timeout may occur. Can you help me handle this situation properly?
QMutexLocker can be used in such situations
this my exampl
static QMutex mainMutex;
.....
MainController* MainController::s_instance = NULL;
.....
MainController* MainController::getInstance()
{
if(!s_instance){
QMutexLocker lock(&mainMutex);
if(!s_instance){
s_instance = new MainController;
}
}
return s_instance;
}
getInstance() function directly called from different threads.

synchronizing slots in QThread -?

I have 2 slot handlers in QThread- derived class: one is timer handler and another is just asynchronous callback handler. Both have to modify the same data.
struct somedata {
int max;
int min;
double avg;
}
...
class MyThread: QThread {
private:
somedata m_data;
private Q_SLOTS:
void asyncCallback(int a, int b) {
m_data.max += a;
m_data.min += b;
}
void timer() {
m_data.avg =(m_data.a + m_data.b)/2;
}
}
Should the access to m_data be serialized in some fashion, although both method are in the same thread?
Thanks,
As long as you can guarantee that your data is only ever being accessed or modified by a single thread at any time, then you don't need to work about synchronizing access to that data via thread-safety constructs.
One way to verify this is to check the return value of QThread's static currentThread() function when your functions are called.
If both functions are called by same thread then you dont need to worry about the data getting changes when the other call is in the second slot.If you are not sure then the best option is to use a mutex in both the slots so that only one process or changes the value of m_data.

Resources