Events

From Rosetta Code
Revision as of 23:53, 4 July 2009 by rosettacode>Kevin Reid (add Haskell example)
Task
Events
You are encouraged to solve this task according to the task description, using any language you may know.

Event is a synchronization object. An event has two states signaled and reset. A task may await for the event to enter the desired state, usually the signaled state. It is released once the state is entered. Releasing waiting tasks is called event notification. Programmatically controlled events can be set by a task into one of its states.

In concurrent programming event also refers to a notification that some state has been reached through an asynchronous activity. The source of the event can be:

  • internal, from another task, programmatically;
  • external, from the hardware, such as user input, timer, etc. Signaling an event from the hardware is accomplished by means of hardware interrupts.

Event is a low-level synchronization mechanism. It neither identify the state that caused it signaled, nor the source of, nor who is the subject of notification. Events augmented by data and/or publisher-subscriber schemes are often referred as messages, signals etc.

In the context of general programming event-driven architecture refers to a design that deploy events in order to synchronize tasks with the asynchronous activities they must be aware of. The opposite approach is polling sometimes called busy waiting, when the synchronization is achieved by an explicit periodic querying the state of the activity. As the name suggests busy waiting consumes system resources even when the external activity does not change its state.

Event-driven architectures are widely used in GUI design and SCADA systems. They are flexible and have relatively short response times. At the same time event-driven architectures suffer to the problems related to their unpredictability. They face race condition, deadlocking, live locks and priority inversion. For this reason real-time systems tend to polling schemes, trading performance for predictability in the worst case scenario.

Variants of events

Manual-reset event

This event changes its state by an explicit request of a task. I.e. once signaled it remains in this state until it will be explicitly reset.

Pulse event

A pulse event when signaled releases all tasks awaiting it and then is automatically reset.

Sample implementations / APIs

Show how a manual-reset event can implemented in the language or else use an API to a library that provides events. Write a program that waits 1s and then signals the event to a task waiting for the event.

Ada

Ada provides higher-level concurrency primitives, which are complete in the sense that they also allow implementations of the lower-level ones, like event. Here is an implementation of the manual-reset event.

The event interface: <lang ada> protected type Event is

  procedure Signal;
  procedure Reset;
  entry Wait;

private

  Fired : Boolean := False;

end Event; </lang> The event implementation> <lang ada> protected body Event is

  procedure Signal is
  begin
     Fired := True;
  end Signal;
  procedure Reset is
  begin
     Fired := False;
  end Reset;
  entry Wait when Fired is
  begin
     null;
  end Wait;

end Event; </lang> With the event defined above: <lang ada> with Ada.Text_IO; use Ada.Text_IO;

procedure Test_Events is

  -- Place the event implementation here
  X : Event;
  task A;
  task body A is
  begin
     Put_Line ("A is waiting for X");
     X.Wait;
     Put_Line ("A received X");
  end A;

begin

  delay 1.0;
  Put_Line ("Signal X");
  X.Signal;

end Test_Events; </lang> Sample output:

A is waiting for X
Signal X
A received X

AutoHotkey

<lang AutoHotkey> SetTimer, internal, 1000 Return

internal:  ; fire on a timer

 TrayTip, internal, internal event!`npress F2 for external event
 SetTimer, internal, off

Return

F2::  ; external event: fire on F2 key press

 TrayTip, external, f2 key pressed

Return </lang>

E

<lang e>def makeEvent() {

   def [var fired, var firer] := Ref.promise()
   
   def event {
       to signal() {
           firer.resolveRace(null) # all current and future wait()s will resolve
       }
       to reset() {
           if (firer.isDone()) { # ignore multiple resets. If we didn't, then
                                 # reset() wait() reset() signal() would never
                                 # resolve that wait().
               # create all fresh state
               def [p, r] := Ref.promise()
               fired := p
               firer := r
           }
       }
       to wait() {
           return fired
       }
   }
   
   return event

}</lang>

The event object has this behavior: the return value of .wait() will be resolved after the time of the earliest .signal() for which there is no intervening .reset().

<lang e>def e := makeEvent()

{

   when (e.wait()) -> {
       println("[2] Received event.")
   }
   println("[2] Waiting for event...")

}

{

   timer.whenPast(timer.now() + 1000, def _() {
       println("[1] Signaling event.")
       e.signal()
   })
   println("[1] Waiting 1 second...")

} </lang>

Haskell

<lang haskell>import Control.Concurrent (threadDelay, forkIO) import Control.Concurrent.SampleVar

-- An Event is defined as a SampleVar with no data. -- http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent-SampleVar.html newtype Event = Event (SampleVar ())

newEvent = fmap Event (newEmptySampleVar) signalEvent (Event sv) = writeSampleVar sv () resetEvent (Event sv) = emptySampleVar sv waitEvent (Event sv) = readSampleVar sv</lang>

<lang haskell>main = do e <- newEvent

         forkIO (waitTask e)
         putStrLn "[1] Waiting 1 second..."
         threadDelay 1000000 {- µs -}
         putStrLn "[1] Signaling event."
         signalEvent e
         threadDelay 1000000 {- µs -}    -- defer program exit for reception

waitTask e = do putStrLn "[2] Waiting for event..."

               waitEvent e
               putStrLn "[2] Received event."</lang>

Note: Because there is no serialization of the text output, there is a chance that it will appear interleaved.

Tcl

Tcl has been event-driven since 7.5, but only supported channel and timer events (plus variable traces, which can be used to create event-like entitites). With the addition of coroutines, it becomes much simpler to create general events:

Works with: Tcl version 8.6

<lang tcl># Simple task framework built from coroutines proc pause ms {

   after $ms [info coroutine];yield

} proc task {name script} {

   coroutine $name apply [list {} \
       "set ::tasks(\[info coro]) 1;$script;unset ::tasks(\[info coro])"]

} proc waitForTasksToFinish {} {

   global tasks
   while {[array size tasks]} {

vwait tasks

   }

}

  1. Make an Ada-like event class

oo::class create Event {

   variable waiting fired
   constructor {} {

set waiting {} set fired 0

   }
   method wait {} {

while {!$fired} { lappend waiting [info coroutine] yield }

   }
   method signal {} {

set wake $waiting set waiting {} set fired 1 foreach task $wake { $task }

   }
   method reset {} {

set fired 0

   }

}

  1. Execute the example

Event create X task A {

   puts "waiting for event"
   X wait
   puts "received event"

} task B {

   pause 1000
   puts "signalling X"
   X signal

} waitForTasksToFinish</lang>

Output:

waiting for event
signalling X
received event

Of course, the classic way of writing this is much shorter, but intermingles the tasks: <lang tcl>after 1000 set X signalled puts "waiting for event" vwait X puts "received event"</lang>