Event Loop
This guide gives an overview of how the event loop is implemented.
Overview
class Async::Reactor
provides the event loop and sits at the root of any task tree. Work is scheduled by adding class Async::Task
instances to the reactor. When you invoke Async::Reactor#async
, the parent task is determined by calling Async::Task.current?
which uses fiber local storage. A slightly more efficient method is to use Async::Task#async
, which uses self
as the parent task.
require 'async'
def sleepy(duration, task: Async::Task.current)
task.async do |subtask|
subtask.annotate "I'm going to sleep #{duration}s..."
subtask.sleep duration
puts "I'm done sleeping!"
end
end
def nested_sleepy(task: Async::Task.current)
task.async do |subtask|
subtask.annotate "Invoking sleepy 5 times..."
5.times do |index|
sleepy(index, task: subtask)
end
end
end
Async do |task|
task.annotate "Invoking nested_sleepy..."
subtask = nested_sleepy
# Print out all running tasks in a tree:
task.print_hierarchy($stderr)
# Kill the subtask
subtask.stop
end
Thread Safety
Most methods of the reactor and related tasks are not thread-safe, so you'd typically have one reactor per thread or process.
Embedding Reactors
Async::Scheduler#run
will run until the reactor runs out of work to do. To run a single iteration of the reactor, use Async::Scheduler#run_once
.
require 'async'
Console.logger.debug!
reactor = Async::Reactor.new
# Run the reactor for 1 second:
reactor.async do |task|
task.sleep 1
puts "Finished!"
end
while reactor.run_once
# Round and round we go!
end
You can use this approach to embed the reactor in another event loop. For some integrations, you may want to specify the maximum time to wait to Async::Scheduler#run_once
.
Stopping Reactors
Async::Reactor#stop
will stop the current reactor and all children tasks.
Interrupting Reactors
Async::Reactor#interrupt
can be called safely from a different thread (or signal handler) and will cause the reactor to exit with an Interrupt
exception.