Pages

Callback for a multithreaded GUI

In a previous post I showed a brilliant little multithreaded application where two threads, driven by two buttons, were competing on the same resource, a label displaying a number that changed accordingly to the requests of the competing threads.

That application was kind of elegant, using the Qt slot mechanism to connect the working threads with the main window GUI. Alas it was wrong, too. You could not see the error in such a simple application that apparently works correctly, but the point is that simply using "emit" in a thread to connect to another function is not enough to make this other function to run in the same thread. What happened there was that the called function was running in the same thread (the main application thread) whatever was the calling thread. Not exactely what was expected.

So far about the bad news. Good news is that is not a big issue rewriting the code so that it works as I wanted. Here we see how to do it using the good old C++ callback mechanism for classes. By the way, if you need to call just a free function from different threads, you could have a look at another post, that shows exactely that.

The basic idea is that our increasers should know how to call from their run() function - the actual function that runs in an own specific thread - the function in MainWindow that modifies the label. We can easily get this passing to the class a pointer to the MainWindow object itself:
#ifndef INCREASER_H
#define INCREASER_H

#include <QThread>

class MainWindow; // 1.

class Increaser : public QThread
{
public:
Increaser(int step);
void run();
void stop();
void restart();

static void setCallback(MainWindow* that); // 2.
private:
bool isRunnable_;
int step_;

static MainWindow* that_; // 3.
};

#endif // INCREASER_H

1. We don't need to know all the MainWindow class details at this point, we just need to know that we are talking about a class.
2. I assume that we want all the increasers working on the same instance of MainWindow (quite reasonable in this case), so the setCallback() method is static, it has to be called just once for all the increasers.
3. As said above, all the increasers share a reference to the same MainWindow object.

The implementation code for the Increaser class does not change much, but I report it here all of it, just fof clarity sake:
#include "increaser.h"
#include "mainwindow.h" // 1.
#include <iostream>

MainWindow* Increaser::that_ = NULL; // 2.

void Increaser::setCallback(MainWindow* that) { that_ = that; } // 3.

Increaser::Increaser(int step) : isRunnable_(true), step_(step) {}

void Increaser::run()
{
while(true)
{
std::cout << "Step: " << step_ << " in "
<< this->currentThreadId() << std::endl;
msleep(500);
if(!isRunnable_)
break;
if(that_) // 4.
that_->changeValue(step_);
}
}

void Increaser::stop()
{
isRunnable_ = false;
wait();
}

void Increaser::restart()
{
isRunnable_ = true;
QThread::start();
}

1. Here we need the class details, so that the compiler could actually check that really MainWindow has a public method as the one we claim that we want to use.
2. that_ is a static member of the Increaser class, so we have to define it somewhere in the code. Here is a good place. Notice that we explicitely initialize it to NULL: no MainWindows object is available if not set by someone else.
3. And here is the static method that initialize the Increaser static member.
4. The MainWindow changeValue() method is called only if we actually have a valid reference to an object of that type.

The changes in the MainWindow class are even lighter ones.

In the class declaration the method changeValue() is not anymore defined as a slot, but as a "normal" public member:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

// ...

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
// ...

void changeValue(int);

private slots:
// ...
};

#endif // MAINWINDOW_H

The class constructor now sets the callback for the increasers:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow),
increaser_(2), decreaser_(-3), curValue_(100)
{
ui->setupUi(this);
ui->lbValue->setText(QString::number(curValue_));

Increaser::setCallback(this);
}

And here is again the function called by the incresers:
void MainWindow::changeValue(int step)
{
QMutexLocker ml(&mCV_);

std::cout << "change value in " << QThread::currentThreadId() << std::endl;
ui->lbValue->setText(QString::number(curValue_ += step));
}

Running the application we can now see how the thread id is the one of the calling thread. So we can finally say that we have got what we wanted.

Go to the full post

Excluding from build path

This post has been improved and moved to Biting Code, my blog dedicated to JVM languages.Now it show how to exclude a file from build when working with Eclipse, IntelliJ, and NetBeans.

Go to the full post

Moving a file around

In my current project I use subversion, and TortoiseSVN, as a versioning tool. It is not the first time I am using it, but I am not yet an expert SVN user. For instance, last day it was the first time I bumped in a file-moved issue.

A file, let's call it one.txt, was happily sitting in the doc folder for a while, when someone decided it had to move somewhere else, say in a subdirectory named sub.

Moving a file with tortoise is very easy. Right click on it from the explorer, go in the TortoiseSVN submenu, Rename it specifying the new path name, in this case "sub/one.txt", and finally commit the change to subversion. Notice that "sub" should be an already existing directory, both in the working area and in the repository.

A glitch could arise is another user is working on the same file, and try to save it to the old location after it has been moved to the new one.

User 'B' changes one.txt in doc, and then try to commit. To his surprise, he got an error and a suggestion to update his working files. But the update leads to another error: subversion detects a conflict between doc/one.txt in the working area and doc/sub/one.txt in the repository.

At this point, tortoise let us to decide what to do: do we want to override the decision of the first user and move back the file to the original position, or do we accept the move? Both alternatives are supported.

Go to the full post