Search code examples
c++macosqtsignals-slotsqtimer

Why does QTimer::singleShot block my main thread at time 1 but not 0


I have been debugging some code which seems to be locking the main thread when the GUI is not visible. I have stripped it right back to the following code snippet and I have identified that a problem exists around my implementation of QTimer::singleShot.

To replicate the issue I set the timer to 1 (millisecond) start the application looping and send the app window to to the background. Eventually the app will grind to a halt until the UI is brought into the foreground.

If I set the timer to 0, it then the runs flawlessly regardless of where the main app window is located, I have increased the loop size as far as 4 million without issue.

I guess what I am stuck with is, what is the difference between

QTimer::singleShot(1, this, SLOT(emptyListOfAnotherObjects()));

and

QTimer::singleShot(0, this, SLOT(emptyListOfAnotherObjects()));?

Why would one block the main thread over the other?

For what it is worth running this loop in a different thread, keeping the GUI separate, results in the same issue.

class AnotherObject : public QObject
{
    Q_OBJECT

public:
    AnotherObject();

    void process();

signals:
    void done();
};

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void start(bool);

private slots:
    void emptyListOfAnotherObjects();

    void delayEmptyOfAnotherObject();

private:
    QList<AnotherObject*> m_listOfAnotherObject;
    Ui::MainWindow *ui;
};

==

AnotherObject::AnotherObject()
{
    qDebug() << "CTOR AnotherObject" << this;
}

void AnotherObject::process()
{
    emit done();
    deleteLater();
}

//==

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(ui->start, SIGNAL(clicked(bool)), this, SLOT(start(bool)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::start(bool)
{
    for(long i=0; i<40000; i++){
        m_listOfAnotherObject.append(new AnotherObject());
    }

    emptyListOfAnotherObjects();
}

void MainWindow::emptyListOfAnotherObjects()
{
    if(m_listOfAnotherObject.isEmpty()) {
        qDebug() << "List empty, done.";
        return;
    }

    AnotherObject* i = m_listOfAnotherObject.takeFirst();
    connect(i, SIGNAL(done()), this, SLOT(delayEmptyOfAnotherObject()));
    i->process();

    qDebug() << m_listOfAnotherObject.count();
}

void MainWindow::delayEmptyOfAnotherObject()
{
    QTimer::singleShot(1, this, SLOT(emptyListOfAnotherObjects()));
}

Thanks

Update:

Sorry, should have added that this is Qt 5.2 on OSX.


Solution

  • App Nap is likely the cause of this problem.

    I suspect that using invokeMethod places a message on the thread's event queue, which prevents the app from napping. In contrast, using QTimer waits and checks the timeout, so an empty event queue will allow it to enter the nap state.

    Anyhow, it is possible to disable app nap from within your application, as described here.

    As you're using Qt, you'll want to use a .mm file to encapsulate the objective-c code.