A C++ Wrapper for WaitForMultipleObjects Part II - Adding Timer Support






4.71/5 (8 votes)
Describes a C++ class that encapsulates the WaitForMultipleObjects API usage pattern making it easy to integrate it with C++ objects.
Introduction
In a
previous article, I had discussed a C++ wrapper around the
WaitForMultipleObjects
API that provides a generic interface to the API
which eliminates the need to write the rather ubiquitous switch-case statement to
process its return value and transfer control to the appropriate method or function. The
wrapper allows you to dynamically add and remove wait handles associating a function object
as the handler function for a wait handle. In this article I'll be adding Win32
timer support to this class so that class clients can register function objects
to be called when a preset time interval has elapsed.
Background
It is typical of most software to have routine processing tasks. Examples for such tasks are housekeeping functions that perform clean up of unused resources or a function to monitor the health of connected clients in a network application. A common programming idiom used to implement this sort of mechanism is the timer which involves invoking functions when a preset time interval has elapsed. Timers can be fully self managed where a worker thread, amongst its other duties, keeps routinely checking if one of the required timer tasks ought to be invoked. If the program has GUI, Windows provides timer messages that will be sent to the specified window at a preset interval where the required processing can be undertaken. Yet another alternative is if the underlying operating system provides the necessary API, that can be used to register callbacks which will be invoked when the specific time interval has elapsed. Windows SDK provides a few functions that implement the last style of timer mechanism.
The Windows SDK timer APIs provide for two types of timers -- manual-reset and synchronization timers. The former type exhibits a behavior that is quite similar to Win32 events in that they can be waited upon in a wait function to be signalled. When the preset time elapses their state changes to signalled and the wait function which is monitoring the handle returns. The latter uses Asynchronous Procedure Calls (APC) to implement their functionality and requires the thread that is managing the timers to be in, what Microsoft defines as an alertable wait state. When the timer interval has elapsed an APC is queued to the thread APC queue which is serviced when the thread enters an alertable wait state.
Using Windows SDK provided timers are inheretntly more efficient as they are built into the scheduler. Using them also leads to somewhat simpler and cleaner code as the operating system will do the hard work of keeping track of the elapsed interval and when to invoke the timer function (or signal the timer object). Furthermore, multiple timer objects can be created and managed independently which helps achieve better encapsulation.
For integrating timer functionality into WFMOHandler, I'll be using the manual-reset timers.
I chose this as they are closer to waitable objects in behavior which made the
implementation similar to that of WaitHandler<>
. Furthermore, from the MSDN documentation
for SetWaitableTimer,
this approach seemed to provide better guarantee of timer delivery than synchronous timers.
The down-side is that every timer would occupy a slot in the limited WaitForMultipleObjects
handle array thereby reducing the number of wait handles that can be monitored.
Using the Code
Timer support is added through two additional methods -- AddTimer()
and
RemoveTimer()
.
AddTimer()
is a function template and has a signature quite similar to
AddWaitHandle()
. Besides the function object that is the timer handler which will be
called when the timer expires, it takes two additional parameters; the timer interval to be
specified in milliseconds and a boolean that specifies whether this is a one-off timer or a repeat
timer. As the name implies, one-off timers are just that -- timer function object is invoked once
after the preset interval elapses and the timer is no longer usable and is disposed.
Repeat timers, on the other hand, work like the good old GUI windows timer, routinely invoking
the timer function object until it is explicitly stopped. The function returns an unsigned
,
which is the unique id of the timer just created. This id can be later used to stop the timer by
supplying it to the RemoveTimer()
method.
To use, declare your class deriving it from WFMOFHandler
and register the timers
that you need by supplying the timer handler function or method as a function object. As with wait
handles, this argument can be an explicitly constructed function object or one that is composed on the
fly using the STL std::bind()
facility. Both these two usages are shown below in the
MyDaemon
constructor.
#include "wfmohandler.h"
// A simple class that implements an asynchronous 'recv' UDP socket.
// Socket binds to loopback address!
class AsyncSocket {
USHORT m_port;
WSAEVENT m_event;
SOCKET m_socket;
// hide default and copy ctor
AsyncSocket();
AsyncSocket(const AsyncSocket&);
public:
AsyncSocket(USHORT port)
: m_port(port)
, m_event(::WSACreateEvent())
, m_socket(::WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, 0))
{
// bind the socket
struct sockaddr_in sin = {0};
sin.sin_family = AF_INET;
sin.sin_port = ::htons(port);
sin.sin_addr.s_addr = ::inet_addr("127.0.0.1");
if (m_event != NULL && m_socket != INVALID_SOCKET
&& ::bind(m_socket, reinterpret_cast<const sockaddr*>(&sin), sizeof(sin)) == 0) {
// put it in 'async' mode
if (::WSAEventSelect(m_socket, m_event, FD_READ) == 0)
return;
}
std::cerr << "Error initializing AsyncSocket, error code: " << WSAGetLastError() << std::endl;
// something went wrong, release resources and raise an exception
if (m_event != NULL) ::WSACloseEvent(m_event);
if (m_socket != INVALID_SOCKET) ::closesocket(m_socket);
throw std::exception("socket creation error");
}
~AsyncSocket()
{
::closesocket(m_socket);
::WSACloseEvent(m_event);
}
// for direct access to the embedded event handle
operator HANDLE() { return m_event; }
// Read all incoming packets in the socket's recv buffer. When all the packets
// in the buffer have been read, resets the associated Win32 event preparing it
// for subsequent signalling when a new packet is copied into the buffer.
void ReadIncomingPacket()
{
std::vector<char> buf(64*1024);
struct sockaddr_in from = {0};
int fromlen = sizeof(from);
int cbRecd = ::recvfrom(m_socket, &buf[0], buf.size(),
0, reinterpret_cast<sockaddr*>(&from), &fromlen);
if (cbRecd > 0) {
std::cerr << cbRecd << " bytes received on port " << m_port << std::endl;
} else {
int rc = ::WSAGetLastError();
if (rc == WSAEWOULDBLOCK) {
// no more data, reset the event so that WaitForMult..will block on it
::WSAResetEvent(m_event);
} else {
// something else went wrong
std::cerr << "Error receiving data from port " << m_port
<< ", error code: " << ::WSAGetLastError() << std::endl;
}
}
}
};
class MyDaemon : public WFMOHandler {
AsyncSocket m_socket1;
AsyncSocket m_socket2;
AsyncSocket m_socket3;
class AnotherEventHandler { // an explicit functor
AsyncSocket& m_socket;
public:
AnotherEventHandler(AsyncSocket& sock) : m_socket(sock) {}
void operator()() {
m_socket.ReadIncomingPacket(); // handle incoming socket data
}
};
AnotherEventHandler m_aeh;
public:
MyDaemon()
: WFMOHandler()
, m_socket1(5000)
, m_socket2(6000)
, m_socket3(7000)
, m_aeh(m_socket3)
{
// setup two handlers on the two AsyncSockets that we created
WFMOHandler::AddWaitHandle(m_socket1,
std::bind(&AsyncSocket::ReadIncomingPacket, &m_socket1));
WFMOHandler::AddWaitHandle(m_socket2,
std::bind(&AsyncSocket::ReadIncomingPacket, &m_socket2));
WFMOHandler::AddWaitHandle(m_socket3, m_aeh);
// install two timers -- a repeat timer and a one-off timer
// note how the timer ids are preserved for removal later (if necessary)
m_timerid = WFMOHandler::AddTimer(1000, true,
std::bind(&MyDaemon::RoutineTimer, this, &m_socket1));
m_oneofftimerid = WFMOHandler::AddTimer(3000, false,
std::bind(&MyDaemon::OneOffTimer, this));
}
virtual ~MyDaemon()
{
Stop();
// just being graceful, WFMOHandler dtor will cleanup anyways
WFMOHandler::RemoveWaitHandle(m_socket2);
WFMOHandler::RemoveWaitHandle(m_socket1);
}
void RoutineTimer(AsyncSocket* pSock)
{
pSock;
std::cout << "Routine timer has expired!" << std::endl;
}
void OneOffTimer()
{
std::cout << "One off tmer has expired!" << std::endl;
m_oneofftimerid = 0;
}
};
The code above is the same as what was used for the previous article save for the addition of two methods and hooking them up with the new timer API. Both use cases of the timer API -- one off timer and repeat timer -- are exhibited in the sample.
Points of Interest
Unlike the wait handles, timer handles are managed internally by WFMOHandler. When a timer is
added, a new Win32 timer object is created and added to the wait handle array. This handle is not
visible to the class client and is released when the timer is removed by a corresponding call to the
RemoveTimer()
method. However, for one-off timers, the timer handle will automatically
be released after the timer function returns irrespective of whether RemoveTimer()
is called or not.
Under the hood, the timer interface is implemented using the same technique as what is employed
for wait handles. However, the inner class template, TimerHandler<>
, used for generating
timer specific class instances, adds an extra class, TimerIntermediate
, to the
derivation chain. On the surface, this class would appear to have been created to hold the unique
id of the timer which is used to track the removal request from client classes and might even seem
redundant as the timer id can be stored in the template class TimerHandler<>
. However,
there is another reason why this additional class is there in the derivation chain of
TimerHandler<>
.
Having TimerIntermediate
in the derivation chain helps us distinguish these
objects from WaitHandler<>
class instance objects from the common
m_waithandlers
collection. This is necessary as in RemoveTimer()
we need to locate the TimerHandler<>
class instance object for the timer id that
is being requested to be removed. However, since the objects corresponding to all wait handles
that we track are stored uniformly, as a pointer to the base class WaitHandlerBase
,
we need a mechanism to differentiate the two children. This is where having
TimerIntermediate
in the derivation chain comes handy. Since all the
TimerHandler<>
instances also have a TimerIntermediate
in the derivation
chain, we can distinguish the two by using dynamic_cast<TimerIntermediate*>
to cast
the WaitHandlerBase
pointer upwards to TimerIntermediate
.
Note that we could have achieved the objective of distinguishing the two children of
WaitHandlerBase
by adding an extra method to the class that returns a boolean by
default. TimerHandler<>
can then override this method and return a different
value to identify itself. Sample implementation is shown below.
// base class for waitable triggers
struct WaitHandlerBase {
HANDLE m_h;
bool m_markfordeletion;
WaitHandlerBase(HANDLE h) : m_h(h), m_markfordeletion(false)
{}
virtual ~WaitHandlerBase()
{}
virtual bool IsTimer() { return false; }
virtual void invoke(WFMOHandler*) = 0;
};
template<typename Handler>
struct TimerHandler : public WaitHandlerBase, public TimerIntermediate {
...
virtual bool IsTimer() { return true; }
...
}
However, this entails changing the base class design to accommodate a derived class' requirement. Something that is to be ideally avoided if possible. Of course this would be the more efficient approach of the two, but since removing of timers is typically not an oft repeated task in a program, I felt such a design (or compromise of pure design, rather) was not warranted.
Caveats
One caveat of the implementation is that once a timer is created, its duration cannot be changed.
If the timer interval is to be changed, the only solution is to remove the existing timer and then
add a new timer with the new interval. Of course this behavior can be altered though this was enough
for my needs as I developed this class. Implementing this would however require the
TimerIntermediate
class to be extended so that it maintains the timer interval as
well as the repeat flag so that these properties can be updated from the relevant method.
History
07 Jan 2014 - Intial release