JAN
14
2010

A Simple Threading Example

A topic that I've not mentioned in any of my blog posts is threading, not because I have anything against it, simply because a simple use-case hadn't come up. Today I was coding something easy to describe, where using threads was a good solution, so let's take a look at it.

The problem I needed to solve was to calculate cryptographic hashes of files that could be very large - for example 4 gigabyte DVD isos. As with any graphical application it is very important to ensure that the GUI remains responsive while this work is done. Since the problem involves very little communication between the part of the application calculating the hash, and the rest of the code a worker thread is an ideal solution.

The code that actually does the work is very simple, it uses the handy QCryptographicHash class to do the work (stored in the variable hasher):

HasherThread::HasherThread(QObject *parent) :
    QThread(parent)
{
    hasher = new QCryptographicHash( QCryptographicHash::Sha1 );
}

void HasherThread::run()
{
    QFile f( filename );
    if ( !f.open(QIODevice::ReadOnly) ) {
        emit error( QString("Unable to open file %1").arg(filename) );
        return;
    }

    hasher->reset();

    char buffer[16*1024];
    qint64 count;
    do {
        count = f.read( buffer, sizeof(buffer) );
        if ( count == -1 ) {
            emit error( QString("Read error") );
            break;
        }
        hasher->addData( buffer, count );
    } while( !f.atEnd() );

    emit completed( hasher->result().toHex() );
}

The code opens the file to be hashed, then reads through it in 16K chunks which it passes to the hasher. If this code were executed in the application's gui thread then this would cause the interface to freeze until the hash was ready, but the code lives in a class that inherits QThread.

One important feature to note about the code above is that the communication with the rest of the application is via signals (the error and completed signals to be precise). This is very important as it means that when we use the class, we can simply use it like this:

    hasherThread = new HasherThread( this );
    connect( hasherThread, SIGNAL(error(const QString &)),
             ui->resultEdit, SLOT(setText(const QString &)) );
    connect( hasherThread, SIGNAL(completed(const QString &)),
             ui->resultEdit, SLOT(setText(const QString &)) );

The special thing in the code above, is that we didn't have to do anything special - Qt just solved our inter-thread communication issues for us. What I mean, it that if our code had simply tried to output the result by calling ui->resultEdit->setText() directly then we would be attempting to access the GUI from outside the GUI thread which would most likely cause our application to crash. Instead, since we're using the default type of connections 'AutoConnection', what this means is that Qt will spot if the thread in which a signal is emitted is different from the one in which it is received and handle the necessary synchronisation for us - nice!

Comments

Nice post! I think this would be a nice example to put into techbase.

The only thing missing is how to start a thread.
Not hasherThread->run() but hasherThread->start() does the trick. :-)


By vdboor at Thu, 01/14/2010 - 16:31

Good point, I should have mentioned that.


By Richard Moore at Thu, 01/14/2010 - 17:00

I always wondered how does Qt detect this? Is there a field in every QObject that refers to the thread it's owned by?


By Mathias Panzenböck at Thu, 01/14/2010 - 16:40

Yes, that's right. You can query the thread in which a QObject lives using QObject::thread().


By Richard Moore at Thu, 01/14/2010 - 17:01

Aren't Queued Connections only used if the emitting object has been created in the context of a different thread or has been moved to a different thread?

If you create the object and connect within the same thread context (here that's probably the main thread), aren't both connects Direct?


By krake at Sat, 01/16/2010 - 19:57

You could have used QtConcurrent::run() and QFuture.


By bugmenot at Thu, 01/14/2010 - 17:24

Yes, that would work too. I think it's probably overkill for a simple case like the above though.


By Richard Moore at Fri, 01/15/2010 - 12:40

I find that weird. I think it's overkill to create a new class when a simple function (and the help of QtConcurrent::run() and QFuture) would do the job just fine.


By bugmenot at Fri, 01/15/2010 - 19:45

I can see where you're coming from, but using QtConcurrent would introduce a whole new framework to what was a pretty simple example. It would also mean that the Qt build would require exceptions to be enabled. The concurrent framework is nice, but I wouldn't add a dependency to it for something as simple as the example I was covering.


By Richard Moore at Fri, 01/15/2010 - 23:08

QtConcurrent does not necessarily run the code in a different thread it can use the calling thread.

I had been using QtConcurrent::run() in the KResource Akonadi bridge plugins assuming that would execute the code given to run() in a separate thread, however several crash reports later with backtraces clearly indicating the opposite I changed to explicit threading to be safe.


By krake at Sat, 01/16/2010 - 20:01

Pages