Simple Event Framework - Suggestions?

M

Mark

Hi,
I'm looking for some ideas on how to build a very simple Event processing
framework in my C++ app. Here is a quick background ...

I'm building a multithreaded app in C++ (on Linux) that
uses message queues to pass pointers to Events between threads. In my app
there are simple events that can be defined using an enum (for example an
event called NETWORK_TIMEOUT) and more complex events that contain data
(for example an event called ALARM that contains a timestamp).

I've hacked together a quick solution that uses an Event class that
contains an enum identifying the type of event (ie. NETWORK_TIMEOUT,
ALARM, etc). For the more complex events that contain extra data a
subclass is created (ie. AlarmEvent) that contains the data. This
requires the thread that is receiving the event to check the enum and downcast
if necessary.

void processEvent(Event *event)
{
if(event->getEventType() == ALARM)
{
AlarmEvent *alarmEvent = static_cast<AlarmEvent *>event;
// process AlarmEvent here...
}
}

Does anyone have any suggestions on a better way to do this? The
downcasting seems a bit messy so maybe someone can suggest a more elegant
and typesafe way of doing this?

Thanks,
Mark
 
J

Jeff Schwab

Mark said:
Hi,
I'm looking for some ideas on how to build a very simple Event processing
framework in my C++ app. Here is a quick background ...

I'm building a multithreaded app in C++ (on Linux) that
uses message queues to pass pointers to Events between threads. In my app
there are simple events that can be defined using an enum (for example an
event called NETWORK_TIMEOUT) and more complex events that contain data
(for example an event called ALARM that contains a timestamp).

I've hacked together a quick solution that uses an Event class that
contains an enum identifying the type of event (ie. NETWORK_TIMEOUT,
ALARM, etc). For the more complex events that contain extra data a
subclass is created (ie. AlarmEvent) that contains the data. This
requires the thread that is receiving the event to check the enum and downcast
if necessary.

void processEvent(Event *event)
{
if(event->getEventType() == ALARM)
{
AlarmEvent *alarmEvent = static_cast<AlarmEvent *>event;
// process AlarmEvent here...
}
}

Does anyone have any suggestions on a better way to do this? The
downcasting seems a bit messy so maybe someone can suggest a more elegant
and typesafe way of doing this?

Thanks,
Mark

C++ has great built-in support for working with types, so work-arounds
like the "switch-on-type" approach you've described have become very
unfashionable. A better approach is to define a different class to
represent each type of Event, and overload the processEvent function for
each different type:

namespace Events
{
class Alarm { /* Data relevant to Alarm event. */ };
class NetworkTimeout { /* No extra data needed */ };
// Other event types...

void process( Alarm const& ) { /* ... */ }
void process( NetworkTimeout const& ) { /* ... */ }
// Functions to proces other event types...

template< typename Event >
void process( Event const& ) { /* Process generic event. */ }
}

Does this meet your needs?

-Jeff
 
M

Mark

Hi Jeff,

I hadn't considered this method, but it looks quite simple. I'm not sure
how it will work with my multithreaded app though because Events are
written onto a message queue and processed in another thread. I'm using an
EventDispatcher class that allows threads to register for certain events,
ie. one thread registers for ALARM events and another for NETWORK_TIMEOUT
events. When an event is generated, the EventDispatcher will send the
event to the appropriate thread(s). My EventDispatcher class doesn't know
about any concrete Events, only the Event base class. This way if I add a
new type of Event down the road I don't have to modify the EventDispatcher.

Given this info will your technique still work? Unfortunately my
experience with templates has been limited to simple std::vector<int> type
stuff.

Thanks Jeff I appreciate it!

Mark
 
J

Jeff Schwab

Mark said:
Hi Jeff,

I hadn't considered this method, but it looks quite simple. I'm not sure
how it will work with my multithreaded app though because Events are
written onto a message queue and processed in another thread. I'm using an
EventDispatcher class that allows threads to register for certain events,
ie. one thread registers for ALARM events and another for NETWORK_TIMEOUT
events. When an event is generated, the EventDispatcher will send the
event to the appropriate thread(s). My EventDispatcher class doesn't know
about any concrete Events, only the Event base class. This way if I add a
new type of Event down the road I don't have to modify the EventDispatcher.

Given this info will your technique still work? Unfortunately my
experience with templates has been limited to simple std::vector<int> type
stuff.

Thanks Jeff I appreciate it!

Mark

Thanks for explaining; no, the technique I described won't work in this
case. Would it be possible to make "process" a virtual method of the
Event base class? This is a common way of implementing the sort of
polymorphism you seem to need. E.g.:

struct Event
{
virtual void process( ) =0;
};

struct Alarm: Event
{
void process( ); // Overrides Event::process( ).
private:
// Data relevant to Alarm event.
};

struct NetworkTimeout: Event
{
void process( ); // Overrides Event::process( ).
};

// Other event types...
 
M

Mark

Thanks for explaining; no, the technique I described won't work in this
case. Would it be possible to make "process" a virtual method of the
Event base class? This is a common way of implementing the sort of
polymorphism you seem to need. E.g.:

struct Event
{
virtual void process( ) =0;
};

struct Alarm: Event
{
void process( ); // Overrides Event::process( ).
private:
// Data relevant to Alarm event.
};

struct NetworkTimeout: Event
{
void process( ); // Overrides Event::process( ).
};

// Other event types...

Looks like the Command pattern. This would be a nice solution, except
that the Event and the processing associated with the event can vary
independantly. For example I have one thread that is managing a socket
connection and another that is managing a log. Both register to receive
the ALARM event but the processing done is different in each case (ie.
send a message over the socket, write an entry into the log). Maybe I'm
going about the solution the wrong way. I considered having a separate
Command pattern type class that is passed into the Event when registered,
but that would require writing *alot* of small classes which doesn't
appeal to me. Maybe I'll come with something better tomorrow, I've been
at it a while today...

Thanks!
Mark
 
J

Jeff Schwab

Mark said:
Looks like the Command pattern. This would be a nice solution, except
that the Event and the processing associated with the event can vary
independantly. For example I have one thread that is managing a socket
connection and another that is managing a log. Both register to receive
the ALARM event but the processing done is different in each case (ie.
send a message over the socket, write an entry into the log). Maybe I'm
going about the solution the wrong way. I considered having a separate
Command pattern type class that is passed into the Event when registered,
but that would require writing *alot* of small classes which doesn't
appeal to me. Maybe I'll come with something better tomorrow, I've been
at it a while today...

Aha! Sounds like you need call-backs: just one more level of
indirection.

For each type of thread, define a struct to provide handlers for all the
relevant types of Event. Provide a virtual method in the Event class;
inside the virtual method, the exact type of the Event can be known, and
the event can call back the appropriate routine from the Thread's
handler struct.

I've coded an approach here that assumes the Event module knows nothing
about what types of Thread may exist. The approach relies on two levels
of run-time indirection. If you're willing to couple the Events more
strongly to the Thread types, you can get away with only one level of
run-time indirection by overloading the Event::call method for each type
of Thread handler; that way, the Handler methods don't need to be
virtual, and in fact the base Handler class need not exist.

Please let me know if I've explained this poorly, or if there is any
problem with the code that makes this approach inappropriate.

-Jeff


namespace Events
{
struct Alarm;
struct Network_Timeout;

struct Handler
{
virtual void handle( Alarm& ) =0;
virtual void handle( Network_Timeout& ) =0;
};

struct Event
{
virtual void call( Handler& ) =0;
};

struct Alarm: Event
{
void call( Handler& h ) { h.handle( *this ); }
private:
// Alarm-specific data.
};

struct Network_Timeout: Event
{
void call( Handler& h ) { h.handle( *this ); }
};
}

#include <iostream>

namespace Threads
{
struct Socket_Connection
{
struct Handler: Events::Handler
{
void handle( Events::Alarm& )
{
std::cout <<
"A Socket_Connection thread is handling an "
"Alarm event.\n";
}

void handle( Events::Network_Timeout& )
{
std::cout <<
"A Socket_Connection thread is handling a "
"Network_Timeout event.\n";
}
};

void process( Events::Event& event )
{
Handler handler;

event.call( handler );
}
};

struct Log_Manager
{
struct Handler: Events::Handler
{
void handle( Events::Alarm& event )
{
std::cout <<
"A Log_Manager thread is handling an "
"Alarm event.\n";
}

void handle( Events::Network_Timeout& event )
{
std::cout <<
"A Log_Manager thread is handling a "
"Network_Timeout event.\n";
}
};

void process( Events::Event& event )
{
Handler handler;

event.call( handler );
}
};
}

int main( )
{
Threads::Socket_Connection socket_connection;
Threads::Log_Manager log_manager;

Events::Alarm alarm;
Events::Network_Timeout network_timeout;

Events::Event* events[ ] = { &alarm, &network_timeout };

for( Events::Event** p = events; p < events + 2; ++p )
{
socket_connection.process( **p );
log_manager.process( **p );
}
}
 
A

Alf P. Steinbach

Mark said:
I'm looking for some ideas on how to build a very simple Event processing
framework in my C++ app.

There is no such thing as a simple event framework.

...

void processEvent(Event *event)
{
if(event->getEventType() == ALARM)
{
AlarmEvent *alarmEvent = static_cast<AlarmEvent *>event;
// process AlarmEvent here...
}
}
Urgh.




Does anyone have any suggestions on a better way to do this?

Move the downcasting from the client code to the event objects.

Generally that's called the visitor pattern.

It goes sort of like this:


struct EventHandlerBase {};

struct SomeSpecificEvent: GeneralEvent
{
struct HandlerInterface: EventHandlerBase
{
virtual void handle( SomeSpecificEvent const& ) = 0;
};

bool visit( EventHandlerBase& aHandler ) const
{
HandlerInterface* pHandler = dynamic_cast ...
if( pHandler != 0 ) pHandler->handle( *this );
return (pHandler != 0);
}
};


struct ClientObject: EventHandlerBase
{
...
e.visit( *this );
...
};
 
G

galathaea

Mark said:
Looks like the Command pattern. This would be a nice solution, except
that the Event and the processing associated with the event can vary
independantly. For example I have one thread that is managing a socket
connection and another that is managing a log. Both register to receive
the ALARM event but the processing done is different in each case (ie.
send a message over the socket, write an entry into the log). Maybe I'm
going about the solution the wrong way. I considered having a separate
Command pattern type class that is passed into the Event when registered,
but that would require writing *alot* of small classes which doesn't
appeal to me. Maybe I'll come with something better tomorrow, I've been
at it a while today...

What you describe seems like the basic decoupling of the signals /
slots mechanism. This type of thing can be found in several libraries
(boost has a nice one) and can be really simple to drop into your own
app. If you must write it yourself, though, you should at least check
out a few implementations. There are a few basic patterns found in
them (several already discussed) and fixing everything up nicely can
be very instructive.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

galathaea: prankster, fablist, magician, liar
 
M

Mark

Aha! Sounds like you need call-backs: just one more level of
indirection.

For each type of thread, define a struct to provide handlers for all the
relevant types of Event. Provide a virtual method in the Event class;
inside the virtual method, the exact type of the Event can be known, and
the event can call back the appropriate routine from the Thread's
handler struct.

I've coded an approach here that assumes the Event module knows nothing
about what types of Thread may exist. The approach relies on two levels
of run-time indirection. If you're willing to couple the Events more
strongly to the Thread types, you can get away with only one level of
run-time indirection by overloading the Event::call method for each type
of Thread handler; that way, the Handler methods don't need to be
virtual, and in fact the base Handler class need not exist.

Please let me know if I've explained this poorly, or if there is any
problem with the code that makes this approach inappropriate.

-Jeff


namespace Events
{
struct Alarm;
struct Network_Timeout;

struct Handler
{
virtual void handle( Alarm& ) =0;
virtual void handle( Network_Timeout& ) =0;
};

struct Event
{
virtual void call( Handler& ) =0;
};

struct Alarm: Event
{
void call( Handler& h ) { h.handle( *this ); }
private:
// Alarm-specific data.
};

struct Network_Timeout: Event
{
void call( Handler& h ) { h.handle( *this ); }
};
}

#include <iostream>

namespace Threads
{
struct Socket_Connection
{
struct Handler: Events::Handler
{
void handle( Events::Alarm& )
{
std::cout <<
"A Socket_Connection thread is handling an "
"Alarm event.\n";
}

void handle( Events::Network_Timeout& )
{
std::cout <<
"A Socket_Connection thread is handling a "
"Network_Timeout event.\n";
}
};

void process( Events::Event& event )
{
Handler handler;

event.call( handler );
}
};

struct Log_Manager
{
struct Handler: Events::Handler
{
void handle( Events::Alarm& event )
{
std::cout <<
"A Log_Manager thread is handling an "
"Alarm event.\n";
}

void handle( Events::Network_Timeout& event )
{
std::cout <<
"A Log_Manager thread is handling a "
"Network_Timeout event.\n";
}
};

void process( Events::Event& event )
{
Handler handler;

event.call( handler );
}
};
}

int main( )
{
Threads::Socket_Connection socket_connection;
Threads::Log_Manager log_manager;

Events::Alarm alarm;
Events::Network_Timeout network_timeout;

Events::Event* events[ ] = { &alarm, &network_timeout };

for( Events::Event** p = events; p < events + 2; ++p )
{
socket_connection.process( **p );
log_manager.process( **p );
}
}

Hi Jeff,

Thanks again for posting this, it's always interesting to look at various
alternatives. I like this solution, the only possible disadvantage I see
is that adding a new Event requires modifying the Handler class with a new
handle() method. Maybe I'll try something like this out and see how it
works in my app. I could also accomplish the same thing with the Signals
and Slots library that the other poster mentioned, but neither the Boost
or the libsigc++ implementation is thread safe.

Thanks! I'll post again if I have any other ideas or questions about your
solution.

Mark
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,159
Messages
2,570,883
Members
47,414
Latest member
djangoframe

Latest Threads

Top