11.1 Event-Driven Programming, Asynchronous Events, and Concurrency
Key Takeaways
- In an event-driven program, an event loop waits for events and calls the handler registered for each one, so the order of execution depends on the order of events.
- To trace event-driven code, apply the handlers one at a time in the order the events occur, carrying shared state from one handler to the next.
- Asynchronous events can arrive at any time and in any order, causing bugs such as processing a double click twice or using data before a request has finished.
- A race condition occurs when concurrent code reads and writes shared data and the result depends on timing; two updates to count ← count + 1 can overlap so that one is lost.
- Fixes include disabling a control while it is being processed, checking state before acting, making updates atomic with locks, and handling events through a queue.
What this competency asks
ETS asks you to know the concepts of event-driven programs that respond to external events (for example, sensors, messages, and clicks). Beyond tracing, finding inputs for given outputs, describing purpose, and supplying missing code, you should be able to:
- Identify possible errors due to asynchronous events.
- Identify aspects of concurrency in event-driven programming.
How event-driven programs work
A traditional program runs from top to bottom. An event-driven program instead waits for events and reacts to them.
| Part | Role |
|---|---|
| Event | Something that happens: a click, a key press, a timer tick, a sensor reading, an arriving message |
| Event handler (callback) | The procedure that runs when a particular event occurs |
| Registration | Connecting a handler to an event source, such as "when this button is clicked, call onSubmit" |
| Event loop | Runs continuously, takes the next event (often from a queue), and calls its handler |
Examples
- Scratch: when green flag clicked, when this sprite clicked, when I receive [message]
- JavaScript:
button.addEventListener("click", handleClick) - A microcontroller: when the motion sensor detects movement, turn on the light
- A chat app: when a new message arrives, display it
Games, graphical interfaces, web pages, mobile apps, and Internet of Things devices (Section 15.3) are all event-driven.
Tracing event-driven code
Handlers share state through variables. To trace, apply the handlers in the order the events occur.
int count ← 0
void onAddClick ( )
count ← count + 1
print count // print a space after the value
end onAddClick
void onResetClick ( )
count ← 0
end onResetClick
Events: Add, Add, Reset, Add, Add.
| Event | Handler | count after | Printed |
|---|---|---|---|
| Add | onAddClick | 1 | 1 |
| Add | onAddClick | 2 | 2 |
| Reset | onResetClick | 0 | — |
| Add | onAddClick | 1 | 1 |
| Add | onAddClick | 2 | 2 |
Output: 1 2 1 2. A different event order gives different output from the same code. That is the defining feature of event-driven programs.
Working backward: to produce the output 1 2 3, the events must be three Add clicks with no Reset between them.
Errors caused by asynchronous events
Events are asynchronous: they can arrive at any time, in any order, and sometimes while other work is still in progress.
| Problem | What happens | Typical fix |
|---|---|---|
| Double submission | A user clicks "Pay" twice quickly, and two orders are charged | Disable the button while processing; ignore repeats with a state flag |
| Using data before it arrives | A handler displays results before a network request finishes, showing empty or old data | Update the display in the handler for the "data loaded" event |
| Unexpected order | A "Stop" event arrives before "Start" has finished setting up | Check the current state in every handler |
| Event during a long task | The interface freezes, or events pile up | Keep handlers short; move long work to a background task |
| Stale sensor readings | A value changes between being read and being used | Read once into a local variable, then use that copy |
| Missed or dropped events | Events arrive faster than they can be handled | Queue events; limit how often they are sampled |
Concurrency
Concurrency means several tasks are in progress during the same period. They may be interleaved on one processor or run truly in parallel on several cores. Event handlers, timers, background downloads, and threads can all run concurrently and share data.
Race conditions
A race condition occurs when the result depends on the unpredictable timing of concurrent operations on shared data. The statement count ← count + 1 is really three steps: read count, add 1, write the result.
| Time | Task A | Task B | count in memory |
|---|---|---|---|
| 1 | reads 5 | 5 | |
| 2 | reads 5 | 5 | |
| 3 | writes 6 | 6 | |
| 4 | writes 6 | 6 |
Two increments ran, but count rose by only 1. One update was lost. Run 1,000 increments in each of two tasks, and the total may come out below 2,000, and differently each time.
Preventing concurrency errors
- Mutual exclusion (locks): only one task at a time may run the code that updates shared data.
- Atomic operations: use an increment that the system guarantees cannot be interrupted.
- Avoid shared mutable state: give each task its own data, or pass messages instead of sharing variables.
- Single-threaded event queues: many interface frameworks run all handlers one at a time on a single thread, which avoids races among handlers but makes long handlers freeze the interface.
Deadlock is another concurrency hazard: two tasks each hold a resource the other needs and wait forever. Acquiring resources in a consistent order prevents it (Section 15.2).
Classroom connection
Block-based environments make event-driven ideas concrete. Two sprites that both respond to "when green flag clicked" run concurrently. Students quickly see that the result can depend on which script runs first, which is a natural first encounter with race conditions.
A program has two handlers that share int count ← 0. onAddClick adds 1 to count and prints count; onResetClick sets count to 0 and prints nothing. The user clicks Add, Add, Reset, Add. What is printed?
An online store sometimes charges customers twice because they click the "Place order" button twice before the first click finishes processing. Which change best fixes this asynchronous-event problem?
Two concurrent tasks each execute total ← total + 1 one thousand times on a shared variable that starts at 0. The final value is sometimes less than 2,000. What best explains this?
Which statement best describes an event-driven program?