Introduction To Amp Event Loop
Event Loop
All asynchronous magic would be impossible without Even loop. It is the core of any asynchronous application. We register events and handlers for them. When an event is fired the event loop triggers an appropriate handler. This allows a caller to instantiate an operation and continue without waiting for this operation to be completed. Later the caller will be notified about completion.
Before we start I want to point that in JavaScript we have event loop out-of-box, that means that we even don’t care that exists. But in PHP things are different. We have to create it manually. In Amp, event loop is available globally via static methods provided by Amp\Loop
class.
So, let’s start with some Hello world examples.
Defer Code
To run some code inside the loop we can use Loop::run()
method. It accepts a callback. Then this callback deferred.
This snippet is not really very interesting, the code here looks synchronous even if it uses an event loop. When running this script we receive an expected output:
But it perfectly illustrates the integration of the event loop into a synchronous PHP script. Everything before the loop executes synchronously as it is. Then event loop receives flow control and executes everything inside it. When all schedules tasks are done (or you explicitly stop the loop with Loop::stop()
call), the flow control leaves the loop and continues synchronously executing the script.
Now, let’s try something more complicated:
With this script we now can see asynchronous execution and that the flow has changed:
That happens because when we schedule some code with Loop::defer()
this code is deferred to execute in the next iteration of the event loop. In our example, the first iteration of the loop has one echo 'inside loop' . PHP_EOL
call. The scheduled code will be executed when all code in the first iteration is done.
Actually Loop::run()
implicitly defers passed callback. This can be demonstrated by scheduling a callback before running the loop:
The output shows that the first deferred callback is executed before the callback, which is passed to Loop::run()
call:
Delay Code
Now its time to write Amp version of JavaScript setTimeout()
call:
To delay some code event loop has delay()
method. Like in JavaScript it accepts a number of milliseconds and a callback:
Execute it and we receive exactly the same results as with JavaScript! Asynchronous code, cool!
Repeat Code
We can rewrite one more JavaScript function with Amp: setInterval()
. It has the same set of arguments as setTimeout()
does. It also schedules a specified callback, but instead of executing it once, this callback is being repeatedly executed after a specified period of time.
To repeatedly execute some code event loop has repeat()
method:
This is Amp version of this JavaScript setInterval()
call:
If you run this code you will see that it endlessly spams your terminal with Hello world
string. Why?
Do you remember how event loop works? It takes the flow and executes all scheduled tasks. Loop::repeat()
call will endlessly schedule a task until you explicitly cancel it. Behind the scenes, Loop::repeat()
creates a timer watcher and returns its id. This id is also passed to a specified callback as a first argument. So, to cancel this timer you should explicitly call Loop::cancel()
and provide a watcher’s id:
The code above output Hello world
five times and then cancels the timer.
The same result can be achieved by stopping the loop:
Note, that
Loop::cancel()
orLoop::stop()
doesn’t immediately break the callback. That is why we can see exactly 5Hello world
messages.
Watchers
Every time when we schedule some code with defer()
, repeat()
or delay()
behind the scenes event loop creates a timer watcher. This timer watcher contains information about callback, data associated with it, timer id, and the way this callback will be executed (once, once after a given time or repeatedly). All watchers can be canceled via Loop::cancel()
, but in situations when you need to repeatedly cancel and register them it is preferred to pause and then resume the watcher.
Loop::disable($watcherId)
method pauses a watcher with a specified id. To resume a paused watcher use Loop::enable($watcherId)
:
In the snippet above we schedule code echo 'Repeat' . PHP_EOL
to repeatedly execute every half a second. Then we set up two delays: the first one pauses our repeated code, then the second one resumes it. If you run this code you will see the following:
Scheduled echo
statement executes twice and then the watcher is paused. Then we resume a watcher and in continues spamming with Repeat
string.
It’s important to always cancel persistent watchers once you’re finished with them or you’ll create memory leaks in your application.
What Is Hidden Behind Event Loop?
Do I need to install additional extensions to make all this magic work? Not necessary. You can download Amp via composer and start writing asynchronous code, no additional extensions are required. While there are several extensions with event-loop implementations: pecl/ev, pecl/event, php-uv, none of them is required. And Amp has drivers for all of them. Basically, the main difference between different loop implementations lays in performance characteristics. Behind the scenes, Amp\Loop
is clever enough to detect your environment and to choose the best available driver for it. Also, it is OK, if you don’t have any installed extensions, in this case, Amp will use NativeDriver
. While each implementation of the event loop is different, your code should not depend on the particular loop implementation.
Conclusion
This was an introduction to event loop basics. We have started writing asynchronous code by scheduling some code. Event loop is a core of every asynchronous application. It registers events and when these events are fired it triggers appropriate handlers (callbacks). You may consider event loop as a task scheduler. When for example, we delay()
some code, the event loop registers a timer watcher. When a timer is out (the event has happened) event loop dispatches an associated with this timer callback (our delayed code). Once there are no more registered events event loop is done and stops, the flow control returns back to a synchronous PHP script.
You can find examples from this article on GitHub.