9

I'm beginner learning Qt, and trying to understand a Qt provided example for download operation. In downloadmanager.cpp, a member function is the following:

void DownloadManager::append(const QUrl &url)
{
    if (downloadQueue.isEmpty())
        QTimer::singleShot(0, this, SLOT(startNextDownload()));

    downloadQueue.enqueue(url);
    ++totalCount;
}
  • I'm confused to why, if downloadQueue is empty, it will need to activate the startNextDownload() before adding the url. (note that: startNextDownload() ends the program if the downloadQueue is empty)
  • I'm unsure why: QTimer::signleShot(x, y, z) has been used at all. As I understand it to be, a timer that activates the slot with delay of 0 millisecond.
  • I could not figure out from looking at Qt Assistant whether singleShot is a one time setup for repeated activation to the slot at given millisecond interval or whether it is one time

Clarification:

I'm a beginner and in examples like:

statement1;
statement2;

I'm used to seeing statement1 running and finishing before moving on to working on statement2. But trying to learn Qt and reading the given example, I see the SLOT(startNextDownload()) being activated after downloadQueue.enqueue(url); has taken place. I am trying to understand why does this work.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
Robert C. Holland
  • 1,651
  • 4
  • 24
  • 57

3 Answers3

4

This queues a callback in the message queue.

The timer immediately elapses, and a message is posted to the message queue. When the process reaches the main loop for the next time, the startNextDownload() function is called. By this time, the URL is in the queue.

The startNextDownload() function is called from the dispatch context, where it is safe to change window contents. This way, the DownloadManager class can be used from a multithreaded application, where the thread starting the download might be running concurrently with the handler for a Paint event. By invoking it from the same thread that would handle Paint events you can be sure that no such event is being processed, and you can update widgets safely.

If a widget needs to be repainted afterwards, it then asks to be repainted, and the OS will send a Paint event if the widget is currently visible.

Simon Richter
  • 28,572
  • 1
  • 42
  • 64
  • Thanks, that clears up a bit. Since I have not done any threaded programming, I'm understanding it as pushing it to the queue of things to do which later will be done one by one. But why is SLOT(startNextDownload() ensured to run after adding the url to downloadQueue (even though the slot appear/pushed to things to do before the adding) ? I also don't understand: what is the main loop and what causes a process to reach the main loop? But I don't know what "When the process reaches the main loop for the next time" means: what is the main loop and what causes it to reach the main loop? – Robert C. Holland Jan 07 '17 at 17:44
  • @RobertC.Holland, wait.. you are right, in a multithreaded application this can stumble over badly. I'd rewrite that as `bool const needToStart = downloadQueue.isEmpty(); downloadQueue.enqueue(url); if(needToStart) QTimer::singleShot(...);`. – Simon Richter Jan 07 '17 at 17:49
  • 1
    @RobertC.Holland, the main loop is where the program is when it is idle. It's an endless loop where it takes the first event from the queue and handles it, and if there are no events left, it asks the OS to give CPU time to other processes or turn off the CPU until something interesting happens. GUI programs usually consist of just a bunch of event handlers, when they return, the program returns to the main loop. – Simon Richter Jan 07 '17 at 17:56
  • Thanks for the explanation. I'm interpreting the program now as such (please correct me if I'm wrong) : `QTimer::singleShot(0, this, SLOT(startNextDownload()));` tells to add/push onto message queue for the main loop in 0 ms. So, the last thing on the message queue was to run the member function `DownloadManager::append()` (where the singleShot does not make a jump to call the slot but merely schedule to run after) and so, the next thing will be `DownloadManager::startNextDownload()` after `DownloadManager::append()` finishes completely. Thank you! – Robert C. Holland Jan 07 '17 at 18:19
  • **The timer is a lie**. The statement that "timer immediately elapses" is false. `QTimer::singleShot(0, ...)` is a misnomer, it does something totally different than every other invocation of `QTimer::singleShot`. It's a special case that means: "do it next time the event loop gets a go". The time figures nowhere, and the fact that Qt even internally implements it using handles named "timers" is misleading: those "timers" don't actually use any platform timers, they are simply handles that the event loop uses. A zero-duration timer is-not-a-timer in Qt, in spite of its name. – Kuba hasn't forgotten Monica Jun 21 '18 at 16:08
  • It is also not true that the OS sends a repaint event when a widget update is scheduled. That'd be wasteful, as the event loop has all the information needed to invoke the repaint - it doesn't need to wait for the native event queue. When you `QWidget::update`, there are no repaint events coming from the OS. On some platforms, the OS might synchronize the top-level window update with vsync, but Qt itself will perform the update of a backing bitmap ahead of time, and the vsync-triggered action will just copy the bitmap to the screen. – Kuba hasn't forgotten Monica Jun 21 '18 at 16:11
  • @KubaOber, these are implementation details that would make the answer more complicated than it needs to be — the behaviour is "as if" the timer immediately elapsed. In the same way, Qt can optimize the path to update widgets if it is safe to do so on the current platform, but some systems do not even have backing bitmaps, so the render target is only available during a paint event. Also, if you modify UI state from outside the main loop, you need an additional lock to synchronize your updates against your other threads. – Simon Richter Jun 21 '18 at 17:02
  • The behavior is not "as if" the timer immediately elapsed, although perhaps it's a detail that nobody cares about. `QAbstractEventDispatcher::aboutToBlock` fires only before real timers elapse. I don't know what you mean by "modify the UI state from outside the main loop" - from outside the main thread? Then, yes, the other threads need to have a lock on the main thread's event loop. Unfortunately, Qt doesn't expose that API, but it's a 5-line affair to add it. You can use finer-grained locks, which are preferable when other threads want to keep on working ASAP. – Kuba hasn't forgotten Monica Jun 21 '18 at 17:43
2

Answer to current question title

Every call to QTimer::singleShot(...) is executed on the event loop of the thread where it is invoked **. If invoked from the main thread, it'll be the event loop started with app.exec().

According to the Qt-Network-Manager-Example, this function is called after the network-manager is filled with the URL's so the single-shot will be processed after the queue has been completely filled. Poorly the qt documentation isn't that clear about this topic yet, so for more information about event processing etc please look here.


Answer for old question title

Before I start, the timer is for having the download in an extra thread. So the GUI keeps responsive.

The complete downloadNext() method is recursive. It will be only called once and called till the queue is empty. See this:

void DownloadManager::append(const QStringList &urlList)
{
    foreach (QString url, urlList)
        append(QUrl::fromEncoded(url.toLocal8Bit())); //Call for only one URL
  ...
}

void DownloadManager::append(const QUrl &url)
{
    if (downloadQueue.isEmpty())
        //I'm only called if the queue is empty! And I will be called after the next line. Not instantly!
        QTimer::singleShot(0, this, SLOT(startNextDownload()));  

    downloadQueue.enqueue(url);
    ++totalCount;
}

After the queue is empty each method returns and at least the message that the download is done will be printed.

So why does this work? Please see first chapter below.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
realvictorprm
  • 614
  • 6
  • 20
  • Thanks for the details, but `QTimer::singleShot(0, this, SLOT(startNextDownload()));` as a separate thread, how is it guaranteed to run before after `downloadQueue.enqueue(url);` has taken place (given it is only one main thread) ? – Robert C. Holland Jan 07 '17 at 18:21
  • Because it runs on the qt event thread, which starts execution after the call to `app.exec()` – realvictorprm Jan 07 '17 at 18:26
  • 1
    I am not sure this is correct. AFAIK the slot will run on whatever thread *this* belongs to. This does not have to be the main UI thread (the function will run in the thread of the receiver according to the documentation). So as currently stated the answer is simply wrong. – EFraim Feb 09 '17 at 12:36
  • Could you refer please to the documentation. – realvictorprm Feb 09 '17 at 14:17
  • @ArthurP.R.: See this http://blog.bbv.ch/2012/10/03/multithreaded-programming-with-qt/ blog post for explanation of thread affinity as currently implemented in Qt. Short story - each QObject has thread affinity, queued signal connections maintain the execution in the correct thread automatically – EFraim Feb 19 '17 at 17:45
  • @EFraim 's right, execution gets scheduled to the QThread's eventloop that "owns" the slot, while `app.exec()` starts the one of the main thread, which in general is a different one. – Jeronimo Jun 21 '18 at 07:33
-2

you can understand things about Class QTimer before you end up with a solution as you desire, please have a look here for your understanding