Activation Timer — a Simple Task Scheduler

Environment: VC++ 6/VC++.NET, Pure Win32 API

Overview

One of the most common problems in Win32 programming is dealing with a number of functions that must be executed each time the specified period expires. For example, you have three routines that must run:

function_1

….every 14 seconds
function_2

….every 11 seconds
function_3

….every 90 seconds

A common solution involves several objects known as timers, which may seem fairly easy but cumbersome to use. This article introduces a simple yet powerful set of classes that greatly reduce the effort (and headache) required to handle multiple periodic jobs.

Algorithm

Another straightforward solution can be the following:

  • For each appended task, create a new thread.
  • Create a separate timer within every thread.
  • Use these timers in a common way.

This may seem great, but only if your system has enough resources to handle as many threads and tasks you wish to run. It is better to use a single timer and open a new thread only when your background task is really about to start. The modified algorithm is:

  • Create a timer.
  • After any task is appended:
    • Kill the timer.
    • Calculate a minimal timeout value = Greatest Common Measure of all timeout values.
    • Restart the timer.
  • Each time the timer activates, check whether any tasks are up to run.
  • If so, create a new thread(s) and run the task(s) in this thread(s).

This is nearly perfect, but one problem remains. If the task’s execution period is less than the task’s working time, a single task can start multiple threads; therefore, we must keep an eye on all of the threads to avoid resource leaks. In other words, we must implement some kind of garbage collection algorithm, which will track all thread objects and will close all unused handles.

The final algorithm is:

  • Create a timer.
  • After any task is appended:
    • Kill the timer.
    • Calculate a minimal timeout value = Greatest Common Measure of all timeout values.
    • Restart the timer.
  • Each time the timer activates, check whether any tasks are up to run.
  • For each ready-to-run task:
    • If a task is run for the first time, create a list of active threads, create and add a single thread object to this list.
    • If the thread list is not empty, append a new thread object to the end of the list.
    • Check all thread objects on the list, except of one recently appended, for completition of execution.
    • If any thread is not active, release its thread handle.

Advantages of this approach:

  • Minimal resource overhead.
  • Elimination of all memory and resource leaks.
  • Relatively high computational efficiency.
  • Ease of use (and debug)—only one timer to track.

Class Hierarchy

The given set consists of five classes; the higher class is always a manager-class (and an aggregate) for a subordinate class.
The actual hierarchy is:


ActivationTimer — manages —> TaskList — contains —>
Tasks — (each task) controls (its own) —>
ThreadList — handles —> Threads

struct Thread // Structure that incapsulates the
// thread handle.
{
// Only Thread list is allowed to create/close threads:

friend class ThreadList;

protected:

// Active thread handle:
HANDLE ThreadHandle;

Thread* Next; // Next thread in a list.
Thread* Prev; // Previous thread in a list.

// Construction / destruction:
Thread(HANDLE hNewThreadHandle);
virtual ~Thread();
};

class ThreadList // Double-linked list of thread objects.
{
// Only task is allowed to handle ThreadList:
friend class Task;

protected:

Thread* Begin; // Head of list
Thread* End; // Tail of list
int NumOfThreads; // Number of threads in a list.

// Construction / destruction:
ThreadList(HANDLE hNewThreadHandle = NULL);
virtual ~ThreadList();

// Add a new thread to the list:
int AppendThread(HANDLE hNewThreadHandle);

// Get the pointer to a ‘num’th thread object:
Thread* operator[] (const int num) const;

// Determine whether ‘num’th thread is active or not:
bool IsThreadActive(const int num) const;

// Close the ‘num’th thread object:
int CloseThreadObject(const int num);

// Get the number of threads in a list:
int GetNumOfThreads() const;
};

class Task // Class that incapsulates task data.
{
// No one is allowed to create and handle Task except for
// TaskList:

friend class TaskList;

protected:

// Time (in milliseconds), at which task is periodically
// performed:

unsigned long timeout;

void* pFunc; // Pointer to the task
// (i.e.: function).

void* pParameter; // Pointer to a *structure*
// parameter of task.

ThreadList* listOfThreads; // List of active threads
// opened by a task.

Task* Next; // Pointer to the next task
Task* Prev; // Pointer to the previous task

// Construction / destruction:
Task(const unsigned long msTimeout,
void* pNewFunc, void* pParam);
virtual ~Task();

void Execute(); // Call a task AND clean the
// threadlist.

};

class TaskList // Double-linked list of
// task data

{
// No one is allowed to create and handle TaskList except
// for ActivationTimer:

friend class ActivationTimer;

protected:

Task* Begin; // The very first Task.
Task* End; // The last Task.

int NumOfTasks; // Number of Tasks in the list.

// Construction / destruction:
TaskList(const unsigned long msTimeout = 0,
void* pNewFunc = NULL,
void* pParam = NULL);
virtual ~TaskList();

// Retrieve a pointer to the ‘num’th task:
Task* operator[] (const int num) const;

// Get the number of tasks:
int GetNumOfTasks () const;

// Get the timeout value for the ‘num’-th task:
unsigned long GetTaskTimeout(const int num) const;

// Add a task:
int AppendTask(const unsigned long msTimeout,
void* pNewFunc, void* pParam);

// Remove a task from the list:
int DeleteTask(const int num);

// Call a task and create an individual thread for it:
void CallTask (const int num);
};

class ActivationTimer // A simple task scheduler –
// designed as a singleton.

{
protected:

static TaskList* listOfTasks; // Pointer to the list
// of tasks.

// Minimal timeout – timer checks tasks for execution every
// ‘minTimeout’ milliseconds:
static unsigned long minTimeout;
static unsigned long maxTimeout; // Maximum timeout in
// the list.

static unsigned long curTimeout; // Current time.
static unsigned int TimerId; // ID of the internal
// timer.

// Check if it is a time to call a task:
static void ExecAppropriateFunc();

// Calculate ‘minTimeout’ value.
void RecalcTimerInterval();

static void CALLBACK TimerProc(HWND, UINT, UINT, DWORD);

public:

// Construction / destruction:
ActivationTimer(const unsigned long msTimeout = 0,
void* pNewFunc = NULL,
void* pParam = NULL);
virtual ~ActivationTimer();

// Add a single task:
int AddTask(const unsigned long msTimeout, void* pNewFunc,
void* pParam = NULL);

// Remove a task:
int RemoveTask(const int cpPos);

// Get the number of active tasks:
int GetNumOfTasks() const;

// Stop the timer:
void Halt();

// Restart the timer:
void Restart();

// Deallocate a singleton object and
// reallocate it once again.

void Reset();
};

Note 1: As you can see, two double-linked lists (the list of tasks and the list of threads for every task) form the foundation of this set of classes. The question may arise: Why not use STL’s <list> container template class? The answer is: STL’s <list> brings not only the <list>, but also a HUGE amount of unnecessary code, which slows and enlarges the application. So I’ve decided to use the most simple (and the most efficient, in our case!) “hand-made” implementation of a double-linked list.

Note 2: Another point of interest—the core class, ActivationTimer, which is implemented as a Singleton. A singleton is a class that assures existence of a maximum of one object of its type at a given time, and provides a global access point to this object. This article gives a comprehensive overview of a singleton design pattern.

Note 3: The real cause of ActivationTimer being a singleton is a stupid limitation of Win32 API: the timer callback procedure, TimerProc, is the only callback function in Win32 that does not take any user-defined argument. This behavior can be overridden in two ways:

  • By using not an ordinary timer (SetTimer / KillTimer), but a special synchronization object—waitable timer
    (CreateWaitableTimer / SetWaitableTimer / CancelWaitableTimer).
    Unfortunately, this approach produces significant performance overhead.
  • By using a special synchronization object, which is new to VC++ 7 – timer-queue timer
    (CreateTimerQueue / CreateTimerQueueTimer / DeleteTimerQueueEx / DeleteTimerQueueTimer).
    Timer-queue timers are lightweight objects that enable you to specify a callback function to be called at a specified time or a period of time. These objects are new to VC++ 7, and I still don’t have the ability to implement them, so … their time is yet to come.

Implementation

Note 4: For better understanding, only member-functions that have a direct influence on the algorithm will be explained here. The source code contains detailed comments on every function.

Step by step:

  • Create a timer:

  • // Create a timer with one task in the task list:
    ActivationTimer::ActivationTimer(const unsigned long msTimeout,
    void* pNewFunc, void* pParam)
    {
    if((msTimeout <= 0UL) || !pNewFunc)
    {
    listOfTasks = new TaskList();
    TimerId = 0;
    minTimeout = 0;
    maxTimeout = 0;
    curTimeout = 0;

    return;
    }

    listOfTasks = new TaskList(msTimeout, pNewFunc, pParam);
    minTimeout = msTimeout;
    maxTimeout = msTimeout;
    curTimeout = msTimeout;

    Restart();
    }

    // Restart the timer:
    void ActivationTimer::Restart()
    {
    TimerId = SetTimer(NULL, NULL, minTimeout, TimerProc);
    }

  • Append a task:

  • // Add a new task to the task list.
    // Return value: zero-based position of task in a task list.

    int ActivationTimer::AddTask(const unsigned long msTimeout,
    void* pNewFunc, void* pParam)
    {
    // A little bit of parameter validation:
    if((msTimeout <= 0UL) || !pNewFunc)
    return -1;

    // Kill the timer:
    Halt();

    // Append a new task:
    int pos = listOfTasks->AppendTask(msTimeout,
    pNewFunc, pParam);

    // Recalculate ‘maxTimeout’ value:
    maxTimeout = maxTimeout < msTimeout ?
    msTimeout : maxTimeout;

    // Recalculate ‘minTimeout’ value:
    RecalcTimerInterval();

    // Recreate the timer:
    Restart();

    return pos;
    }

  • After any task is appended—calculate minTimeout value:

  • // Calculate the value of minTimeout.
    // This is a time when ‘ExecAppropriateFunc()’ routine is
    // periodically called.

    void ActivationTimer::RecalcTimerInterval()
    {
    minTimeout = listOfTasks->GetTaskTimeout(0);
    for(int i = 0; i < listOfTasks->NumOfTasks; i++)
    {
    unsigned long tempTimeout = GCM(minTimeout,
    listOfTasks->
    GetTaskTimeout(i));
    minTimeout = minTimeout >
    tempTimeout ?
    tempTimeout : minTimeout;
    }

    curTimeout = minTimeout;
    }

  • Each time timer activates, check if any tasks are up to run:

  • // Check whether it is a time to call one of the tasks from
    // the task list.

    void ActivationTimer::ExecAppropriateFunc()
    {
    bool reset = true;
    for(int i = 0; i < listOfTasks->NumOfTasks; i++)
    {
    // If time has come, call the ‘i’th task:
    if((curTimeout % listOfTasks->GetTaskTimeout(i)) == 0)
    {
    listOfTasks->CallTask(i);
    reset &= true;
    }
    else
    {
    reset &= false;
    }
    }

    // If all tasks are called simultaneously,
    // reset curTimeout value:

    if(reset)
    curTimeout = minTimeout;
    else
    curTimeout += minTimeout;
    }

  • If so, execute these task(s):

  • // Call a task and create an individual thread for it:
    void TaskList::CallTask(const int num)
    {
    Task* task = operator[](num);
    task->Execute();
    }

    // Create an individual thread for a task:
    void Task::Execute()
    {
    listOfThreads->AppendThread(CreateThread(NULL, 0,
    (LPTHREAD_START_ROUTINE)pFunc,
    pParameter, 0, 0));

    // …and wipe out all unused thread handles:
    int numThreads = listOfThreads->GetNumOfThreads();
    for(int i = 0; i < numThreads – 1; i++)
    {
    if(!(listOfThreads->IsThreadActive(i)))
    listOfThreads->CloseThreadObject(i);
    }
    }

  • On exit, free all the resources, close all thread handles:

  • Task::~Task()
    {
    delete listOfThreads;
    }

    ThreadList::~ThreadList()
    {
    Thread* target = Begin;
    Thread* temp;

    if(target) // If ThreadList is not empty…
    while(target)
    {
    temp = target->Next;

    delete target;

    target = temp;
    }
    }

    Thread::~Thread()
    {
    // Check if thread has exited.
    // If it did not – terminate it.

    DWORD exitCode;
    GetExitCodeThread(ThreadHandle, &exitCode);

    // !!! UNSAFE CODE !!!
    // Try not to use lengthy operations OR
    // use some kind of internal completition
    // flag to explicitly instruct thread to exit.
    // Refer MSDN on hazards of TerminateThread.

    if(exitCode == STILL_ACTIVE)
    TerminateThread(ThreadHandle, 0);

    CloseHandle(ThreadHandle);
    }

Note 5: The Thread::~Thread destructor needs some explanation. Calling TerminateThread can be quite dangerous if the target thread is still running, because:

  • If the target thread is allocating memory from the heap, the heap lock will not be released.
  • If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread’s process could be inconsistent.
  • If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.

A possible solution may be one of the following:

  • Do not use lengthy operations as a thread function.
  • Use a special bool variable (something like: m_bStopExecution), and periodically check the state of this variable.
    If this variable is true, the thread can gracefully quit.
  • Use a synchronization object—for example, semaphore or waitable timer—to instruct a thread to stop execution (just the same as above).

Please refer to MSDN for more information on TerminateThread and synchronization objects.

Usage

To use ActivationTimer, do the following:

  • Include “ActivationTimer.cpp” and “ActivationTimer.h” in your project.
  • Write the following line at the beginning of the “StdAfx.h” file:
  •     #include “ActivationTimer.h”

  • Write the following line at the beginning of the “ActivationTimer.cpp” file:
  •     #include “StdAfx.h”

  • Within the main file of your application, declare:
  •     ActivationTimer actTimer;

  • To add a task with no parameters, write:

  • void MyFunction()
    {
    ….
    }

    ….

    actTimer.AddTask(10000, MyFunction);

    This will periodically execute MyFunction every 10 seconds.

  • To add a task with a single parameter, declare:

  • void MyFunction2(void* pParam)
    {
    int* value = (int*)pParam;

    ….
    }

    ….

    int someValue = … ;

    actTimer.AddTask(15000, MyFunction2, &someValue);

  • If you wish to pass several parameters to your task-function, declare a structure:

  • typedef struct
    {
    int valueA;
    float valueB;
    bool valueC;


    } MyStruct;

    and write:


    void MyFunction3(void* pParam)
    {
    MyStruct* structVal = (MyStruct*)pParam;

    ….
    }

    ….

    MyStruct* somestruct = new MyStruct;

    // initialize struct here

    actTimer.AddTask(5100, MyFunction3, somestruct);

The demo project (see link below) is an ordinary “Hello, World!” application, built with AppWizard, with these examples included. Enjoy!

Acknowledgements

Thank you all the CodeGuru visitors who posted comments, and double-Thank-you to everyone who wrote me, reporting bugs, ideas, and suggestions!

P.S.

Why not a Task Scheduler instead of the Activation Timer? That’s because Windows already has its Task Scheduler application, so I’ve decided not to mess up. At least, Activation Timer application is not included in a standard Windows package, yet……..

Downloads


Download demo project – 12 Kb


Download source – 5 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read