Click here to Skip to main content
Click here to Skip to main content

A Handy Idle-Processing Class for Long Procedures

, 21 Mar 2003
Rate this:
Please Sign up or sign in to vote.
This article introduces a convenient class for hooking into idle processing.

Introduction

This article will introduce a convenient class to handle situations where long procedures must be handled without causing your application to freeze. It is well suited when implementing a thread is not the most desired approach to the problem.

Background

About 8 years ago, when trying to develop a simulator application for Windows, one of the hurdles we had to deal with was long processes. It was no problem creating methods to do the processing, but once you entered the loop, that was it, and we were stuck for the duration. If this was anything more than a few seconds it quickly became a problem, and users would hopelessly tap on the keys or click the mouse in an attempt to abort. At the time, threads were not an option in the design, so another answer had to be found.

The problem

When handling long processes, most often they are the result of intensive looping, and often loops within loops. Once you enter the loop, you don't exit the loop until the entire process is finished. Sometime this process takes minutes or even hours to complete. During the whole time, no messages can be processed by the application and window buttons, controls, toolbars and keys become quite useless. To abort, you need to process the message from one of these, and that requires that at some time the application must return to the message loop.

The solution

In the early development work, we developed everything directly with the Windows API. This meant that we also had to create the message loop. Since we had to return to the message loop, the answer lay in the loop itself. Rather than creating a large outer loop to the process, the natural looping provided by the messages was used to provide support for the long procedure. All that was needed was to hook into it and perform a loop test to decide when we were finished. C++ application objects were written to manage the message loop and its loop task processing. Smaller objects called 'tasks' were created to hook into the loop whenever it was needed.

When MFC came along, the application class was discovered to provide the same basic support in the virtual method called OnIdle(). All that was now required was to keep the hook code and leave the MFC message loop undisturbed. This was particularly attractive because it meant that we could use virtually the same code when the application was recently ported to use MFC. In addition, since we did not resort to multiple message processing points, all the window messaging could remain under the control of MFC.

Even though an idle processing call is provided, its not very convenient. There is no nice way to hook into it and detach from it. What is needed is a structure and a simple object to allow the developer to create sets of code that can be added and removed in a convenient fashion. This way, blocks of execution can be 'packaged' up, processed with a clean interface that is not unlike a thread object. The class that was created for this purpose is called ZTask. Everything in our libraries is prefixed by a Z but you can change it to anything you like. This 'task' object forms the basis of this discussion.

To use the idle processing class, all that is required is a simple change to the derived CWinApp class (In this case called MyApp). Once all the tasks have indicated their completion, this method will return false and cause the application to sleep until the next message arrives. This is the only change required in the application to enable task processing.

//-< OVERRIDE >-------------------------------------------------------------
// OnIdle()
//
// When the message queue is empty, this is time to work on stuff in the
// background. These background activities, or Tasks are called to execute
// one cycle of the work and then return to the queue.
//-------------------------------------------------------------------------
BOOL        MyApp::OnIdle(LONG count)
   {
   return CWinApp::OnIdle(count) || ZTask::executeTasks();
   }

Once tasks are added to the application, through specific methods in the source code, the application then managed the objects automatically. The tasks perform a single pass for each idle call and continue until all the tasks are complete. All that is required is to create these objects to do you work and get them started by adding them to the application.

The tasking objects

Now that we have support in the application, defining the work to be done was clean and easy. A tasking object called ZTask was created to package up individual tasks into nice bundles. The class has virtual methods that are called when it is first added, each time an idle call is made, and one when it is removed. The method for processing a single iteration of the task is called execute(). It is pure and must be overridden when in the derived task. The other two are optional. The execute method will continue to be called in the idle loop until you return true. Once true is returned the task object will be cleaned up and deleted if necessary. All you need to do is decide how much processing you want to perform in a single pass. This will affect the responsiveness of the application.

   //----------------------------------
   // Instance methods
   //----------------------------------
   protected:
      virtual  bool   execute()   = 0;

//-< VIRTUAL >---------------------------------------------
// The task has been added to the execute list
//---------------------------------------------------------
void        ZTask::taskAdded() 
   {
   // Do nothing by default.
   }

//-< VIRTUAL >---------------------------------------------
// The task has been removed to the execute list
//---------------------------------------------------------
void        ZTask::taskRemoved() 
   {
   // Do nothing by default.
   }

Since tasks are executed in the idle processing of the primary thread of the application, it is not predictive when the task will be done. If the task is created on the heap using new then it must also be deleted. Like threads, the task object has a flag that can be set to tell it to delete itself automatically when the task is done, so you don't have to.

Why not threads?

  1. Often program code does not need to run in complete autonomous thread. Rather, what is needed is some means to simply 'defer' the execution until the application is less busy. This keeps the primary thread running crisp and responsive.
  2. It is very often the case that code must run a certain section without intervention (like a CRITICAL section in a thread). Since tasks are running in the primary thread, there is no intervention and you do not need to define these locks.
  3. If you need access to resources or need to pain, you can do it in a thread, but in MFC it is a messy affair since you must attach and detach windows and resources to keep the internal tables in check.

One-Shots

One-Shots are pieces of code that need to be executed asynchronously, but only require a single pass. This is typically what happens when messages are processed such as painting or button clicks. A task object can also be used in exactly this fashion. The big advantage here is all the code associated with the task can be packaged together rather than distributed through window messages. All the task does is return true on the first pass. It is then removed right away by the application object. If the task is embedded as a member of another class, it is not auto-deleted and can be reused over and over by simply adding back to the application whenever needed.

In our applications, one-shot tasks are used extensively as update objects for trees and backing stores for controls and graphs. We do not want these activities to consume CPU time until the message queue is quiet, thus retaining responsiveness in the application. Since potentially dozens of these objects may be present on the display surface, interaction with them is triggering many update tasks that are queued until the application is idle. These objects are part of a library of tools and so they must be able to add the tasks automatically as needed, and without additional coding.

Using tasks

Using the objects depends a lot on your needs. If you want to invoke a task more than once, as in the case of an updater, you may want to construct the object embedded with the auto-delete flag set to false. When you need to start it, you may make a call like this:

ZTask::addTask(this->pUpdater);

When it has completed (by returning true) it will automatically be removed again. We use this strategy to update backing stores for components. If you want to create a task and then forget about it, you would likely create it dynamically and then start it right away. Auto-delete would be turned on in this situation so that the object is cleaned up once it is finished.

ZTask::addTask(new MyTask());

One of the benefits I have found with this approach is, it never runs while a window is scrolling, so it will not interfere with user interaction.

Notes

This class utilized a Vector class that is described in another article that I have already posted. You can easily replace this with a CTypedPtrList if you do not wish to use the Vector class. We utilize our own collection classes for maintenance reasons. The code provided here is well documented and should be easy to follow.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Craig Muller
Web Developer
Canada Canada
Craig Muller has a post graduate degree in electrical engineering and specializes in software development for the power systems industry.
 
His first programming experience was in 1978 writing APL on an IBM-360 linked by a teletype machine. Early programming work was done in 1982-1983 when he developed Fortran programs to solve transfer functions for dynamic systems.
 
Software development was taken up as a career in 1987 while completing a master degree in engineering. Since then he has had 15 years diverse experience in C, C++, Java, MFC, and GUI development applied to power system simulator technology and advanced data visualization tools.
 
Founder of a company called ZSystems in 1998 that specializes in visualization and GUI object development. ZSystems continues to develop software for both Java and C++ applications. Recently, he returned to the Manitoba HVDC Research Centre to manage development of the electromagnetic transients simulator 'PSCAD'.

Comments and Discussions

 
GeneralVery Slick PinmemberKulch24-Mar-03 5:22 
GeneralDialogs and OnIdle PinmemberMarc Clifton23-Mar-03 11:07 
GeneralRe: Dialogs and OnIdle - a better way PinmemberNeville Franks23-Mar-03 22:20 
QuestionSomeone else does this too??? PinmemberMarc Clifton23-Mar-03 11:03 
AnswerRe: Someone else does this too??? PinmemberCraig Muller24-Mar-03 4:25 
Generalsimplistic Pinmemberdog_spawn22-Mar-03 16:37 
GeneralRe: simplistic PinmemberCraig Muller22-Mar-03 20:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 22 Mar 2003
Article Copyright 2003 by Craig Muller
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid