|
|||||||
|
This module provides a simple event framework for the generation, dispatching, and processing of events in an asynchronous manner. Events are generated and dispatched to an event queue explicitly or implicitly via event generators. Processing occurs asynchronously and does not interfere with the dispatching of events.
The framework is suited for applications that generate events which should be processed asynchronously. For example, included in the distribution are two sample applications:
These are just some of the many potential uses of this framework. As you’ll discover, the framework is easy to use and can be applied to a variety of applications. This document also contains numerous examples that can be modified for your own use. Lets move on to a more technical discussion about the framework.
The framework is composed of several classes in the EventFramework module. Although these classes may be used directly, a facade (EventFramework::EventFramework) has been supplied to facilitate use of the framework. The facade has been provided to insulate general use of the framework. However, to effectively use the framework, it is still important to understand how the framework is composed. The diagram below illustrates how each of the classes are related:
|
Generator1 <----+ |
\ gets puts | gets
Generator2 <----+------- Dispatcher ----> Queue <---- Processor
/ |
Generator3 <----+ |
Thread 1 | Thread 2
The following is a brief overview of the significant classes in the framework:
In this section, we will discuss the various ways to utilize this framework using a small handful of examples for illustration. First, lets discuss the code that you’ll actually have to write in order to leverage this framework. In all but the simplest cases, you’ll need to write yourself one or more event handlers that will be used to process events in the system. Handlers can do anything you want. For example, I’ve personally written handlers for the following purposes:
The last two handlers are included in the framework. We‘ll use the print event handler in the first example so we don’t have to concern ourselves with writing our own. In a later example, we’ll chain two handlers together and write our own custom event handler.
The next decision that must be made is how you want to dispatch events into the framework. There are two options for event dispatching:
You can use either method or a combination of both depending on your requirements. The latter is the more hands-off approach as you do not have to concern yourself with the dispatching of events. It also requires that you provide your own event generator. The former is more direct, but you must manually dispatch events yourself. Examples of both are provided in the following sections, as well as examples of custom event handlers and chaining of handlers together.
In this section, a trivial example will be used to illustrate the proper use of the framework when explicity dispatching events. This example will generate three events, dispatch those events, and then terminate after the events have been processed. As each event is processed, a message will be displayed to standard output because each event will specify the use of the PrintEventHandler (which is included in the framework). A custom event handler example will be presented in a subsequent section. The purpose of this example it to emphasize the dispatching of events.
With that said, lets create an array of the three events to be dispatched into the framework:
require 'event' handler = EventFramework::PrintEventHandler.new events = []
0.upto 3 do
events << EventFramework::Event.new(self, 'arbitrary data', handler)
end
As you can see, each event is created by passing a reference to self (the originator), some arbitrary data (in this case a string), followed by our event handler reference. It should be noted that we pass the same event handler to each event. This is more efficient than creating a new event handler object for each generated event (and required if you want to keep state between events).
Now that we have created the three events, we must manually dispatch them into the framework. This is easily accomplished by using the facade that is included with the framework:
facade = EventFramework::EventFramework.new
facade.start
for event in events
facade.dispatch_event event
end
facade.stop
Lets slowly step through this code. First, we instantiate an instance of the facade. Next, we start the framework which will trigger the following chain of events:
After startup, control is immediately returned to the caller. This enables us to manually dispatch our events. As each event is dispatched, it is placed on an event queue, and control is again returned to the caller immediately. At some point, the event will be processed by the event processor (running in a separate thread) which consumes events on the queue. Finally, the framework is stopped in a clean manner. It is important to note that +facade.stop+ will not return until all events on the queue have been processed.
In this section, another trivial example will be used to illustrate the proper use of the framework when implicitly dispatching events using an event generator. This example will generate a single event each time our generator is polled. As each event is processed, a message will be displayed to standard output because each event will specify the use of the PrintEventHandler (which is included in the framework). A custom event handler example will be presented in a subsequent section. The purpose of this example it to emphasize the dispatching of events.
First, lets build our simple event generator. There are two ways to create an event generator:
require 'event'
class TrivialEventGenerator < EventFramework::EventGenerator
def initialize
@handler = EventFramework::PrintEventHandler.new
end
def get_events
[ EventFramework::Event.new(self, 'arbitrary data', @handler) ]
end
end
require 'event'
handler = EventFramework::PrintEventHandler.new
trivial = EventFramework::EventGenerator.new do
[ EventFramework::Event.new(self, 'arbitrary data', handler) ]
end
As you can see, both of our implementations always return an array containing a single event (which is quite trivial but serves the purpose of this example). The event is created by passing a reference to self (the originator), some arbitrary data (in this case a string), followed by our event handler object which is instantiated once in the constructor. Again, it should be noted that we pass the same event handler to each event. This is more efficient than creating a new event handler object for each generated event (and required if you want to keep state between events).
With our event generator complete, we are now ready to utilize the facade interface provided with the framework:
generator = TrivialEventGenerator.new facade = EventFramework::EventFramework.new facade.add_event_generator generator facade.start
begin
while true do sleep 10000 end
rescue Interrupt
facade.stop
end
First, we instantiate an instance of our event generator, followed by an instance of the facade. Next, we add or register our event generator to the facade. At this point, additional generators could be added if desired. Once all generators have been added, the facade must be started. Upon startup, the following chain of events will occur:
After startup, control is immediately returned to the caller so we must be careful to not terminate prematurely otherwise we will not have a chance to see the framework in action. Thus, we sleep for a very long time and loop forever. This prevents the main program from terminating. Furthermore, the loop is wrapped within a begin/rescue block so that keyboard interrupts (Ctl-C) can be caught enabling us to cleanly shutdown the event framework. Again, it should be noted that all events are processed before +facade.stop+ returns control to the caller.
In this example, we’ll create our own custom event handler that will keep state in between invocations. We‘ll assume in this example that the "arbitrary data" that included in each event is a number that is to be summed as each event is processed. Furthermore, the handler will print a message with the current total to the console. There are two approaches we can take when building a custom event handler.
class SimpleEventHandler < EventFramework::EventHandler
def initialize
@sum = 0
end
def handle_event event
puts "sum = #{@sum += event.data}"
true
end
end
handler = SimpleEventHandler.new
sum = 0
handler = EventFramework::EventHandler.new do |event|
puts "sum = #{sum += event.data}"
true
end
In both cases, we now have a custom event handler which can be used in the framework. Readers wondering why handle_event returns true are asked to defer this question until the next section where chaining event handlers are discussed. To complete our example, lets explicitly (for simplicity) dispatch some events to test our custom event handler:
require 'event'
sum = 0
handler = EventFramework::EventHandler.new do |event|
puts "sum = #{sum += event.data}"
true
end
facade = EventFramework::EventFramework.new facade.start facade.dispatch_event EventFramework::Event.new(self, 2, handler) facade.dispatch_event EventFramework::Event.new(self, 1, handler) facade.dispatch_event EventFramework::Event.new(self, 3, handler) facade.stop
When you run this example using either of the two versions of our custom event handler, you should see the following on your screen:
sum = 2 sum = 3 sum = 6
There are times when you may want more than one event handler to process an event. However, the framework enforces (via the Event constructor) that only one handler be associated with each event. This is where the ChainEventHandler comes into play. In this final example, we will discuss how to chain event handlers together using this technique.
First, We‘ll create a simple chain consisting of our custom event handler from the previous section, as well as a PrintEventHandler:
chain = EventFramework::ChainEventHandler.new chain.add_event_handler SimpleEventHandler.new chain.add_event_handler EventFramework::PrintEventHandler.new
Alternatively, you could specify the handlers as arguments to the ChainEventHandler constructor. With our chain, we can now pass it as the single event handler when constructing an event:
require 'event'
class SimpleEventHandler < EventFramework::EventHandler
def initialize
@sum = 0
end
def handle_event event
puts "sum = #{@sum += event.data}"
true
end
end
handler = SimpleEventHandler.new
chain = EventFramework::ChainEventHandler.new chain.add_event_handler SimpleEventHandler.new chain.add_event_handler EventFramework::PrintEventHandler.new facade = EventFramework::EventFramework.new facade.start facade.dispatch_event EventFramework::Event.new(self, 2, chain) facade.dispatch_event EventFramework::Event.new(self, 1, chain) facade.dispatch_event EventFramework::Event.new(self, 3, chain) facade.stop
The above code should output the following to your console:
sum = 2 [Mon Oct 20 16:10:32 EDT 2003 -> main] 2 sum = 3 [Mon Oct 20 16:10:32 EDT 2003 -> main] 1 sum = 6 [Mon Oct 20 16:10:32 EDT 2003 -> main] 3
So how does the ChainEventHandler work? When the chain is invoked, it invokes each of the embedded event handlers until one of them returns a false value at which point the processing of the chain stops. Thus, event handlers that wish to behave appropriately in a chain must return true if subsequent handlers are to process the event. It is for this reason that we return true in our SimpleEventHandler. If we had returned false instead, the PrintEventHandler would never get invoked and the output would be as follows:
sum = 2 sum = 3 sum = 6
Why might you want to prevent subsequent event handlers from running in a chain? Perhaps you want to do some form of rate-limiting. For example, do not print the event unless it has occurred at least three times:
require 'event'
class CountEventHandler < EventFramework::EventHandler
def initialize threshold
@threshold = threshold
@count = 0
end
def handle_event event
@count += 1
@count % @threshold == 0
end
end
chain = EventFramework::ChainEventHandler.new chain.add_event_handler CountEventHandler.new(3) chain.add_event_handler EventFramework::PrintEventHandler.new facade = EventFramework::EventFramework.new facade.start facade.dispatch_event EventFramework::Event.new(self, 1, chain) facade.dispatch_event EventFramework::Event.new(self, 2, chain) facade.dispatch_event EventFramework::Event.new(self, 3, chain) facade.stop
The above will result in the following output:
[Mon Oct 20 16:52:11 EDT 2003 -> main] 3
Although the first two events were dispatched, the CountEventHandler returned false thus terminating the handler chain. As a result, only the third event is actually printed to the console.
The event framework is simple and easy to use. With a few custom event handlers and event generators, you can have a fully autonomous system producing and consuming events asynchronously. Have fun!
| Required files |
| Classes and Modules |