Scopira  20080306
Sidekicks

Introduction

The sidekick ("mini") framework provides developers an easy to use method of performing intensive work in a dedicated worker thread, with the intent of letting the GUI thread continue to maintain the user interface and any user interactions.

Overview

A sidekick-enabled must be broken up into the following componets:

  • The algorithm core must be wrapped in a scopira::core::sidekick_i derived object
  • The GUI thread should peridically monitor the sidekick
  • If there is any common information shared between the GUI and sidekick threads, it must be protected

You often implement this using one of two techniques:

  • the sidekick is "embeded" into your GUI window or view (easier)
  • the sidekick is implemented as a seperate, dedicated class (more modular)

Both techniques are presented below.

Embedding the sidekick into the GUI class

Making your sidekick method

First, make your GUI class (usually the window or widget that will manage the running process visually) a decendant of scopira::core::sidekick_i. Implement the run() method of this interface. The run method should have the form:

   void run(void);

In this method, you will perform the the work that is to be done in the sidekick thread. TODO As the run() method is executed concurently with the GUI thread, great care must be taken to not allow of variable access conflicts. In particular:

  • Try to use only local variables for intermediate results within the run() method
  • You may share startup/initialization shutdown/result variables without fear of conflict, as the worker thread does not exist (or has just finished) when they are accessed by the GUI thread
  • all other "shared" variables must be protected on a mutex (more on this below)

Monitoring the sidekick thread

The GUI thread can monitor the sidekick method's progress.

The simplest case involves simply checking the scopira::core::is_sidekick_running() function during a user initiated event.

To continuous and automically check the status without the user's intervetion, setup a scopira::coreui::uitimer object. This can be used for progress bars, continous plots, text out, etc, for example.

Complex information sharing

The GUI thread and sidekick thread may want to share information during the course of algorithm execution. This is particularly useful for sending intermidate and progress reports to the GUI thread, which can then report back to the user. The user may, via the GUI thread, decide to adjust the running parameters or cancel the job outright.

Any and all shared variables between the GUI and sidekick threads must be protected with a thread scopira::tool::mutex class. This is best done using a scopira::tool::shared_area concept, that encapsulates the data to be shared between two threads and protects and controls access via a mutex:

  struct progress_area {
    int workdone, totalworktodo;
  };

  scopira::tool::shared_area<progress_area> dm_progressarea;

Then, any code segments (both for the GUI thread or run() method thread) should be done via scopira::tool::locker_ptr RIIA idiom object. For example:

... other code ...
{
  scopira::tool::locker_ptr<progress_area> L(dm_progressarea);

  // can now access the shared variables, knowing that the other thread cannot access it at the same time
  return L->workdone / L->totalworktodo;
}
... other code ...

You could forgo the shared_area and locker_ptr classes and just use member variables and one mutex class directly, this is not recommended and is more error-prone.

Using a seperate, dedicated sidekick task

Making a sidekick task

The algorithm must be wrapped in a class that is a decendant of scopira::core::sidekick_i

The GUI thread should then (often, as a result of user-interaction):

  • create an instance of the scopira::core::sidekick_i worker object
  • call any initialization methods (include the constructor)
  • pass the object off to scopira::core::enqueue_sidekick()
  • The sidekick thread will then run the object's run() method
  • You can also directly inspect the sidekick's data (without fear of thread conflicts) after it has completed running (executing it's run() method with the sidekick thread)

Monitoring the sidekick

The GUI thread can monitor the sidekick method's progress.

The simplest case involves simply checking the scopira::core::is_sidekick_running() function during a user initiated event.

To continuous and automically check the status without the user's intervetion, setup a scopira::coreui::uitimer object. This can be used for progress bars, continous plots, text out, etc, for example.

Complex information sharing

The GUI thread can also call methods within the running sidekick task to get more specific information about it's progress. However, as these inspection calls are done in the GUI thread, they may conflict with the sidekick thread who may be updating the same information that is being inspected.

To prevent this from happening, the common area should be in a scopira::tool::mutex-protected area. This can be done with the scopira::tool::shared_area

For example:

  struct progress_area {
    int workdone, totalworktodo;
  };

  scopira::tool::shared_area<progress_area> dm_progressarea;

Then, any code segments should be done via scopira::tool::locker_ptr RIIA idiom object. Your access method (which the GUI thread should call), could look like:

double ui_get_progress(void)
{
  scopira::tool::locker_ptr<progress_area> L(dm_progressarea);

  return L->workdone / L->totalworktodo;
}

Your actual worker code would like this (this would be in the run() method):

   ... algorithm code ...
   {
     scopira::tool::locker_ptr<progress_area> L(dm_progressarea);

     L->workdone += 1;   // did one more task
   }
   ... algorithm code ...

You could forgo the shared_area and locker_ptr classes and just use member variables and one mutex class directly, this is not recommended and is more error-prone.