Developer Documentation
Using threads from within a plugin

When developing plugins for OpenFlipper it is possible to start certain functions in a new thread. Note that when using threading there are a few things the developer has to be aware of:

  • Using threads can cause OpenFlipper to crash since the threading interface is still under development at the moment.
  • The developer should be aware of the fact that threads run necessarily independent from the main thread (handling all of Qt's user interface elements). So accessing any of these gui objects won't be possible from within a thread. This means in general all signals to the OpenFlipper core and all PluginFunctions, always has to be done via a queued signal/slot connection between the thread function and the plugin.
  • In general, you cannot emit any OpenFlipper signal from other threads than the main thread. However, some functions can be safely emitted. In this case, you will find a special remark in the documentation, e.g. LoggingInterface::log() or BaseInterface::updateView()
  • The developer will have to pay special attention to avoid race conditions and dead-lock situations himself since OpenFlipper can obviously not keep track of this.

OpenFlipperThreads in theory and practice

Using threads in OpenFlipper is quite simple. All necessary class definitions are already available to plugins. So the first thing we have to do is instanciate an OpenFlipperThread object and tell the core that we just launched a thread via startJob():

OpenFlipperThread* thread = new OpenFlipperThread("My thread");
emit startJob( "My thread", "My thread's description" , 0 , 100 , false);

The first parameter of the constructor of the OpenFlipperThread class denotes the identifier of the thread. We will use this id to reference our thread later on. The signal startJob() just tells the core that we are about to add a new thread named "My thread" and having "My thread's description" as the description (OpenFlipper displays this in the process manager). The third and fourth parameter determine the progress scale (here going from 0 to 100). This should ideally correspond to the number of steps the thread's process consists of with desireably equal computation time per step. This is later used to inform the user about the progress of our process. The last parameter indicates whether the process should be blocking or not. If it is declared as blocking, all input to OpenFlipper will be ignored until the processing has finished. If a thread is declared as non-blocking, the thread will appear in OpenFlipper's process manager and the user will be able to normally use OpenFlipper while the thread is running in the background.

The next thing we do is connecting the thread's signal to local signals/slots in order to keep track of updates concering the processing of our thread:

connect(thread, SIGNAL(finished(QString)), this, SIGNAL(finishJob(QString)));
connect(thread, SIGNAL(function(QString)), this, SLOT(testFunctionThread(QString)), Qt::DirectConnection);

The thread emits the finished() signal when processing is done and the thread has been destroyed. We need to make sure to at least connect this signal to the global finishJob() signal of the ProcessInterface so that the thread is removed from the process manager window. Additionally, if we want to do additional steps after the processing has finished (e.g. call updatedObject() so that our change to an object are made visible), we can connect finished() to a function that takes care of that.

The function() signal needs to be connected to the function that will actually be processed in the thread. Once having connected these signals, we're about to start our thread:

// Launch the thread
thread->start();
// Start processing of the function code (connected via signal OpenFlipperThread::function())
thread->startProcessing();

Now our function is processed within a new thread and either OpenFlipper's process manager or a blocking widget showing the thread's progress pops up (depending on whether the thread is declared to be blocking or not). So the only thing that is still left to take core of is the continuous update of the thread's progress state. Assuming we run the following function in a separate thread:

void testFunctionThread(QString _jobId) {
for (int i = 0 ; i < 1000000; ++i) {
// Emit new progress state
if(i % 10000 == 0)
emit setJobState(_jobId, (int)(i / 10000));
if(i == 500000) {
// When half of the processing is finished
// change the job's description
emit setJobDescription(_jobId, "Half way done");
}
}
}

This function contains a loop incrementing variable i one million times. Each 10000-th iteration we emit the signal setJobState(QString,int) where we set the second parameter to an integer value between 0 and 100 (remember that we set these limits at the beginning when emitting the startJob() signal). OpenFlipper now updates its progress bar state to the new value (scaled to a percentage of the interval that was defined in startJob). If half of the loop is processed (i == 500000), we additionally want to update the progresses' description that is also displayed in OpenFlipper's progress manager or the blocking widget of the thread, respectively.