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

Threads And Timers

By , 26 Jan 2004
 

Introduction

In this project and in the source files, there is a base class to CWinThread class that will provide you with WM_TIMER messages to your threads. Since WM_TIMER messages are reserved for window objects only, this is a bit tricky to perform. I did this because I was in need of threads that could react to timers but I had lots of threads (100's) to manage and they all read there configuration from the database and set timers based on that configuration. Some threads had 1 timer and other threads had 100's of timers. I needed a way to manage all these timers and events.

Using the code

This class is derived from the standard MFC CWinThread class. m_autoremove will remove all the timers outstanding if you don't. You can turn it off by setting the m_autoremove to false. There are 2 methods AddTimer() and RemoveTimer(). Simple!

// WinTimerThread.h
class CWinTimerThread : public CWinThread
{
    public:
        DECLARE_DYNCREATE(CWinTimerThread)
        //
        CWinTimerThread();
        ~CWinTimerThread();

        //
        bool    m_autoremove;
        //
        UINT_PTR AddTimer(UINT uElapse);
        BOOL RemoveTimer(UINT_PTR idEvent);

};
// WinTimerThread.cpp
...
// Static declarations
static CMapPtrToPtr g_MapTimerID_TO_CWinThreadPtr;

static void CALLBACK TimerProc(
    HWND hwnd,         // handle to window
    UINT uMsg,         // WM_TIMER message
    UINT_PTR idEvent,  // timer identifier
    DWORD dwTime       // current system time 
    )
{
    CWinThread* pThread;

    if (g_MapTimerID_TO_CWinThreadPtr.Lookup((void *) idEvent, (void*&) pThread))
    {
        pThread->PostThreadMessage(WM_TIMER, idEvent, dwTime);
    }
}
// Class 
IMPLEMENT_DYNCREATE(CWinTimerThread, CWinThread)

CWinTimerThread::CWinTimerThread()
{
    m_autoremove = true;
}

CWinTimerThread::~CWinTimerThread()
{
    // check if autoremove is activated
    if (!m_autoremove)
        return;
    // declare local variables
    POSITION pos;
    UINT_PTR idEvent;
    CWinThread* pThread;

    //get the starting point in the list of timer events
    pos = g_MapTimerID_TO_CWinThreadPtr.GetStartPosition();
    //loop while there are list items
    while (pos)
    {
        // get timer event entry
        g_MapTimerID_TO_CWinThreadPtr.GetNextAssoc(pos, 
            (void *&) idEvent, (void*&) pThread);
        // check if the idevent is refering to this object
        if (pThread == this)
        {
           RemoveTimer(idEvent);
        }
    }
}


UINT_PTR CWinTimerThread::AddTimer(UINT uElapse)
{
    UINT_PTR idEvent;

    idEvent = ::SetTimer(NULL, NULL, uElapse, (TIMERPROC) TimerProc);
    g_MapTimerID_TO_CWinThreadPtr.SetAt((void *) idEvent, this);
    return idEvent;
}
BOOL CWinTimerThread::RemoveTimer(UINT_PTR idEvent)
{
    ::KillTimer(NULL, idEvent);
    return g_MapTimerID_TO_CWinThreadPtr.RemoveKey((void *) idEvent);
}

Below is how to use the new CWinTimerThread.

// MyThread.h
#include "WinTimerThread.h"

#if !defined(MYTHREAD_H)
#define MYTHREAD_H
#pragma once

class CMyThread : public CWinTimerThread
{
    public:
        DECLARE_DYNCREATE(CMyThread)
        //
        CMyThread();
        ~CMyThread();

        virtual BOOL InitInstance();
        virtual int ExitInstance();

        void SetLogWindow(CWnd* pWnd);

    protected:
        // 
        CWnd* m_pLogWnd;
        //
        void LogMessage(CString& logmsg);
        void OnTimer(WPARAM idEvent, LPARAM dwTime);
        //
        DECLARE_MESSAGE_MAP()};

#endif // MYTHREAD_H

Notice that all you have to do is call AddTimer() anywhere in the thread and specify the amount of milliseconds, and you're done. The clean up is done for you so you don't even have to worry about it.

// MyThread.cpp
//
#include "stdafx.h"
#include "MyThread.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

IMPLEMENT_DYNCREATE(CMyThread, CWinTimerThread)

CMyThread::CMyThread()
{
}

CMyThread::~CMyThread()
{
}
BEGIN_MESSAGE_MAP(CMyThread, CWinTimerThread)
    ON_THREAD_MESSAGE(WM_TIMER,    OnTimer)
END_MESSAGE_MAP()

BOOL CMyThread::InitInstance()
{
    ...
    //
    AddTimer(1000);
    return TRUE;
}
...

Points of Interest

Posting the WM_TIMER message to the thread should be safe since the numeric value is 0x113, well below WM_USER = 0x400. This is very compatible to the original SetTimer(), KillTimer() and OnTimer(idEvent, dwTime).

Also note that the message to the thread is ON_THREAD_MESSAGE() not the ON_MESSAGE() macro.

History

Version 1.0

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

About the Author

David A. Jones
Web Developer
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralExcellent article; one small quibble on thread safetymemberHal Berman29 May '08 - 6:06 
This class fit the bill perfectly for solving a problem with hung threads in a web server. Thanks for the code and the explanations.
 
One thing I'll mention for the benefit of others who might use this is that the methods of CWinTimerThread that manipulate the map (and TimerProc, too) should be made thread safe so that the map isn't modified or read by more than one thread at a time. One way to do this is to derive a class based on the map that provides a critical section:
 
class CMapPtrToPtrSafe : public CMapPtrToPtr
{
public:	CCriticalSection	m_CritSec;  // to synchronize access to class
};
static CMapPtrToPtrSafe g_MapTimerID_TO_CWinThreadPtr;
Then, in each of the methods of CWinTimerThread that access the map (and in the TimerProc callback, too), the critical section can be locked before the map is accessed and unlocked when done. For example:
 
static void CALLBACK TimerProc(
	HWND hwnd,         // handle to window
	UINT uMsg,         // WM_TIMER message
	UINT_PTR idEvent,  // timer identifier
	DWORD dwTime       // current system time 
	)
{
	CWinThread* pThread;
 
	g_MapTimerID_TO_CWinThreadPtr.m_CritSec.Lock();
 
	if (g_MapTimerID_TO_CWinThreadPtr.Lookup((void*) idEvent, (void*&) pThread))
	{
		pThread->PostThreadMessage(WM_TIMER, idEvent, dwTime);
	}
 
	g_MapTimerID_TO_CWinThreadPtr.m_CritSec.Unlock();
}
Thanks again!
Hal
Generalfrequency of running processmembermehmetned17 Jul '06 - 22:53 
My process have to run 1000 times per second. How is it provided? I don!t know this subject is suitable to forum's subject but I need help. Thanks.
GeneralRe: frequency of running processmemberDavid A. Jones18 Jul '06 - 4:23 
are you going to have a lot of threads? I think that you should have a pool of threads that are initialized and waiting on a queue or mutex. I think that the queue or mutex would then provide some information about what to service. You will have to tune the number of threads and queues to optimize your service requests. Spawning threads will be expensive so they should already be initialized and waiting for something to do.
 
I hope this helps.
 
Cheers,
 
David.
GeneralCWinThread and SetTimermemberkumar_subrahmanya7 Jul '06 - 1:58 
Hi
 
I am also using SetTimer API to create timers in a UI Thread (CWinThread
derived class)
 

The code looks as follows ->
 

m_timerId=::SetTimer(NULL,0,1000,NULL);
where m_timerId is a member data variable.
 

My Message map & onTimer looks as follows.
 

BEGIN_MESSAGE_MAP(CMyThread, CWinThread)
ON_THREAD_MESSAGE(WM_TIMER, OnTimer)
END_MESSAGE_MAP()
 

void CMyThread::OnTimer(WPARAM wParam, LPARAM /*lParam*/)
{
if(wParam==m_timerId)
{
//timer processing code here
 

 
}
}


This code works!!!!!!!!!!!!!!!!
But SetTimer API documentation does not talk about the above usage. (where in both hWnd and callback functions are NULL).
 
Have you tried this kind of usage of SetTimer with CWinThread object?
If yes, do let me know if you have encountered any problems.
 

Thanks in advance

GeneralRe: CWinThread and SetTimer [modified]memberDavid A. Jones12 Jul '06 - 5:20 
Yes there are many issues with what you suggest. Especially if your using threads. You will find that the WM_TIMER message will get dispatched ok if the main UI thread doesn't get locked on other events. Once that UI gets locked on an event your WM_TIMER message will not get dispatched to the other threads. Using CWinTimerThread object this doesn't not happen. The timer dispatch happens inside the windows kernel because it's a call back. A very subtle difference.
 
Another issue is object ownership. In some cases that I found working with threads and having threads spawn threads is that WM_TIMER messages where only getting dispatched to direct children of the UI thread but if a child thread spawned another thread the WM_TIMER message would sometimes not get sent to these child threads. I would consider it a bug in windows but they have this whole "object ownership" paper that I know understand. Handles and other OS objects can not be shared between threads.
 
Anyways, if it was as easy as you suggest I wouldn't have written my article in the first place.
 
My recommendation is to use my object class and enjoy the benefits of it. Smile | :)
 
Cheers,
 
David
 
-- modified at 11:23 Wednesday 12th July, 2006
Generalerror while creating class derived from CWinThreadsussVC++error11 Jul '05 - 10:47 
Hi,

When i try to create a new class derived from CWinThread, the class wizard gives me error like "could not find appropriate header file to include for base class "CWinThread". You may need to manually add the appropriate header files".
 
Can somebody please tell me the reason and aoslution for this?
 
Thanks,
Deepa.

GeneralOnTimer()membermedlar12 Jun '05 - 22:27 
Hello, everyone.this is my class:
#include "SK_ScrollMessage.h"
#ifndef _SK_OSDMANAGER_HEADER_
#define _SK_OSDMANAGER_HEADER_
 
class CHPlayer;
 
class SK_OsdManager
{
public:
SK_OsdManager(CHPlayer *);
~SK_OsdManager();
void playTextOSD( const CString displayText, SK_PlayOSDStyle &playOSDStyle, const CString setFont="宋体",const int frontColor=0,const int backColor=0);
void playBmpOSD( const char *bmpFileName, const SK_PlayOSDStyle playOSDPicture);
private:
long loadBmpFile(char *fileName);
static void WINAPI OnTimer( HWND, UINT, UINT, DWORD);
void playFlashLogo();
char *getFlashLogoFileName();
bool insertLogoFile(char *logoFlashFileName);
private:
CHPlayer *hardPlayer_;
int playOSDFlag_;
bool isUseOSD_;
long osdBufWelcomeText_;
char fileNameBuffer_[100][256];
static int totalFileNum_;
};
#endif
 
question:
 
How can i access the function playTextOSD in OnTimer function.
 
thanks.
 
have a nice day,everybody!
GeneralChanging the AddTimermemberTom Wright10 Mar '05 - 10:10 
David,
If I wanted to add multipule times to my user interface thread, would I just need to make a change to your AddTimer function to include the UINT_PTR nIDEvent? Similar to SetTimer.
 

Would there be anything else that I would need?
Thanks
Tom
 
Tom Wright
tawright915@yahoo.com
GeneralRe: Changing the AddTimermemberTom Wright10 Mar '05 - 11:00 
Okay I got the AddTimer to work with multipule timers, But I'm having a bit of a problem with the RemoveTimer. It seems as though when one timer fires the other will stop until the timer is set.
 
Here is my code:

void CMyThread::OnTimer(WPARAM idEvent, LPARAM dwTime)
{
//CString logmsg;
//

/* logmsg.Format( "ThreadID=%x EventID=%d",
this->m_nThreadID,
idEvent);*/
if (idEvent == 1)
{
RemoveTimer(myHandle, idEvent);
AfxMessageBox("Here is timer 1");
AddTimer(myHandle, 1, 10000);
}
if (idEvent == 2)
{
RemoveTimer(myHandle, idEvent);
AfxMessageBox("Here is timer 2");
AddTimer(myHandle, 2, 2000);
}
 
//LogMessage(logmsg);
}
 

 
Tom Wright
tawright915@yahoo.com
GeneralRequired in Visual Studio 6.0 Env.membersudiptam29 Sep '04 - 1:27 
It is a brilliant example and I require it very urgently . Can you please send me the project build in the Visual Studio 6.0 (VC++) enviroment . It will indeed be a great help .
 
Thanks
GeneralRe: Required in Visual Studio 6.0 Env.memberDavid A. Jones1 Oct '04 - 15:12 
Have you tried creating a new visual studio 6.0 project and compiling the files from my project.
 
David A. Jones
QuestionIs it really possible to create 100's of Timers?memberPersia26 Feb '04 - 2:17 
Hi,
 
Great article, well done!
Just one question. Is it really possible to create 100's of timers (with different counters, e.g. 1=500 ms, 2=1200 ms, etc.) for a thread (or even for entire application)? Isn't there an upper boundary for how may timers one will be able to create?

 
Regards
/Persia
AnswerRe: Is it really possible to create 100's of Timers?memberDavid A. Jones26 Feb '04 - 4:43 
Short Answer: Yes it is possbile to create 100's of timers per thread.
 
Long Answer:
In windows NT, 2000, XP, 2003 any resource such as timers are really just handles to resources and the only limit set for handles is the amount of memory available to the system. Thus the more memory you have the more timers/resource handles you can have.
 
Cheers,
 
David.Roll eyes | :rolleyes:
Generalversion 7.10sussGopal Krishna15 Apr '04 - 3:46 
Hi
Visual Studio.NET can load only version 7.0 project files. ur project is 7.10 version.
Any help on how to compile and run ur project file.
thanks

 
Gopal Krishna
IIT Kanpur
GeneralRe: version 7.10memberDavid A. Jones16 Apr '04 - 2:53 
There are two ways that you can load this project.
 
a) update your Visual Studio.Net to 7.1 by going to the microsoft site and update your Visual Studio.Net as I did
 
b) create your own project empty project and then move the individual source files, then compile
 
Cheers,
 
David.
AnswerRe: Is it really possible to create 100's of Timers?memberDavid A. Jones1 Oct '04 - 15:09 
NO there is no upper limit! It all depends on your computer's resources. Such as CPU speed and memory size.
 

David A. Jones.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 27 Jan 2004
Article Copyright 2004 by David A. Jones
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid