Comments, Code and Qt. Some words about the wonderful world of software engineering

18Sep/115

Easy threading with QtConcurrent::run()

The following scenario is probably familiar to you: you are doing some heavy computing or IO intensive things in your app, like accessing a database, the filesystem or reading data from the Internet, that blocks your UI thread which leads to that the UI freezes. If you are writing a Qt app then probably the first thing that comes to your mind is to subclass QThread and implement the run() method (although you should not do that). But before you do that, you should take a look at Qt Concurrent and what it can offer. There is a good chance that what you need is already provided by the Qt Concurrent framework.

How I added threads to my app in 20 minutes

In this post I want to show you how I added threaded database access to my small podcast application for MeeGo, Podcatcher for N9. The use case I had at hands was that I added automatic cleanup of podcast episodes to Podcatcher for N9, which required some heavy database and filesystem access. This is the (naive) code (in short) that I originally had when cleaning up podcast episodes:

QList<PodcastChannel*> channels = m_channelsModel->channels();

foreach(PodcastChannel *channel, channels) {
   PodcastEpisodesModel *episodesModel = m_episodeModelFactory->episodesModel(channel->channelDbId());
   episodesModel->cleanOldEpisodes(m_keepNumEpisodesSettings, m_autoDelUnplayedSettings);
}


In this case it is easy to see that episodesModel->cleanOldEpisodes() is the method that takes potentially a long time to finish. It will iterate over a list of episodes and delete them from the database if they are too old and also potentially delete some data from the filesystem downloaded to the episode. That's a lot of IO that eventually will block the execution of your main thread, that is also the thread that the Qt UI is running in. And this is also what I saw with my UI when I had a lot of items to iterate over; the UI could be totally frozen for tens of seconds, which is obviously not good.

I fixed this using QtConcurrent::run(). It will place one function into a separate thread and run that function in a separate thread, which frees the UI thread, hence the UI will not freeze. It's really as easy as it sounds. And this suited my use case perfectly, since then I could place the cleanOldEpisodes() method into that separate thread. Perfect!

The only change I had to do to my code was the fact that I would need to iterate over the list of channels and take the next one when the previously thread has finished. This is easy to do. Below is the changed code and all there it so it to have a method running in a separate thread.

QList<PodcastChannel *> m_cleanupChannels = m_channelsModel->channels();
PodcastEpisodesModel *episodesModel = m_episodeModelFactory->episodesModel((m_cleanupChannels.takeLast())->channelDbId());

QFuture future = QtConcurrent::run(episodesModel,
                                   &PodcastEpisodesModel::cleanOldEpisodes,
                                   m_keepNumEpisodesSettings,
                                   m_autoDelUnplayedSettings);

connect(&m_futureWatcher, SIGNAL(finished()),
        this, SLOT(onCleanupEpisodeModelFinished()));
QFutureWatcher m_futureWatcher.setFuture(future);

QtConcurrent::run() takes as the first parameter the object in which the method is that will be run, the second parameter is the actual method to me run (in a bit funny looking syntax, but obviously it's a pointer to the method declaration in the class) and after that run() takes the parameters that the original method that is being run in a separate thread expects when calling it. Compare this to the approach of creating your own thread and having the method run in that method. That would be a lot more writing and refactoring.

When QtConcurrent::run() is executed, it returns a QFuture object, which is basically maps to the execution of the thread that QtConcurrent runs the method in. You give that object to QFutureWatcher to be able to use signals when the thread has finished - and then execute the next item from the list in the thread (why QFuture does not directly have this signal is beyond me, but it must be some implementation issue).

The slot that is called when the thread has finished is below.

void PodcastManager::onCleanupEpisodeModelFinished()
{
    PodcastEpisodesModel *episodesModel = m_episodeModelFactory->episodesModel((m_cleanupChannels.takeLast())->channelDbId());
    QFuture future = QtConcurrent::run(episodesModel, &PodcastEpisodesModel::cleanOldEpisodes, m_keepNumEpisodesSettings, m_autoDelUnplayedSettings);
    m_futureWatcher.setFuture(future);
}

Here we take the next channel object and run it in the same method in a separate thread as before and then set the QFuture of that new thread execution to the QFutureWatcher (but notice that we don't connect any signals anymore, as there is already a connection to this slot made in during the initial run).

This is all there is to it when you want to make a method execution run in a separate thread. And the end result is what I wanted. Previously my UI thread was blocked for tens of seconds in the worst case, and now it all runs smooth as silk.

Practical hints

In my case it was easy to achieve what I wanted in a separate thread since all the heavy stuff was designed to be done behind one method. This method in turn calls other methods to get its job done. This is a good approach and if you use this approach, using QtConcurrent::run() will be very easy for  you.

You also might have noticed that what I am doing is not perfect. There is also the QtConcurrent::map() that will run a method for each of the object in a container. I could have used that as well. But since I got what I wanted with the run() method, I didn't want to change the implementation again.

Unfortunately there is one more thing that you need to take care of and that's concurrent access to shared object. What this means is that if the method, that is being run in a separate thread, uses a shared variable like a method variable for database access, for example, you need to take care of synchronizing the access to this variable. Meaning that only one thread at a time can access the database variable at a time.

This is something I had to take care of: all the PodcastChannelModels instances' methods that will be run concurrently will also access the database with a shared variable - namely QSqlDatabase m_connection. This is because I would only have one connection open to the DB at any given time so the threads must share it. To take care of this sharing, I used QMutex. With a mutex you can lock the access to the shared object for a while during which all other threads trying to access this variable would be sleeping and waiting for a previous thread (that is accessing the variable) to finish. The previous thread would unlock the mutex and the next thread can access the varibale. The QMutex protects the access to this shared resource.

Lucky for us, Qt has made the use of mutex easy. I only needed to surround all access to QSqlDatabase variable with calls to QMutex.lock() and QMutex.unlock() and only one thread at a time can access it.

SomeObject::someMethod() {
...
    mutex.lock();
    QSqlQuery q(m_connection);
    mutex.unlock();
...
}

This method is now getting a query object from the connection object. After this you can unlock the mutex meaning that the object, even if it's run in a thread concurrently, now has its own QSqlQuery object created from the shared QSqlDatabase object.

You need to do the same thing for all instance variables that are used by the method that is being executed with QtConcurrent::run().

Conclusion

Many times when you are thinking of adding threads to your application you really want to run just one method concurrently in a thread. For this QtConcurrent::run() is the best choice available when you are writing a Qt app. It's easy to add to your existing project if you just remember to abstract all the heavy work behind one method.

The price you have to pay for adding threads is that you need to take care of synchronizing access to all the class instance variables that your method is going to touch. But of course, this is not specific to QtConcurrent but to any concurrent programming. Luckily QMutex is easy to use as well and easy to add. If you need more fine grained control to your shared object, consider QMutexLocker, QReadWriteLock, QSemaphore, or QWaitCondition. In simple cases (like I had) QMutex is enough.

You can see the complete source code of the Podcatcher for N9 project here.

Technorati Tags: , , , ,

  • http://artemmarchenko.agilesoftwaredevelopment.com/ Artem

    Nice trick when you just want to run one long operation asynchronously.

  • Mail

    Look for QMutexLock also

  • Andy

    I believe that by calling connect after setFuture you may have created a race condition. setFuture should be called after connect.

    From the documentation:

    void QFutureWatcher::setFuture ( const QFuture & future )

    Starts watching the given future.

    One of the signals might be emitted for the current state of the future. For example, if the future is already stopped, the finished signal will be emitted.

    To avoid a race condition, it is important to call this function after doing the connections.

    • http://www.johanpaul.com/blog/ Johan Paul

      Thanks for pointing this out! I’ll fix the example.

      • Andy

        You’re welcome, Johan.

        What I failed to mention also is that I think that this is a great article. :)