File: getting-started.md

package info (click to toggle)
ruby-async 2.36.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 400 kB
  • sloc: ruby: 1,938; makefile: 4
file content (177 lines) | stat: -rw-r--r-- 6,142 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Getting Started

This guide shows how to add async to your project and run code asynchronously.

Async is a Ruby library that provides asynchronous programming capabilities using fibers and a fiber scheduler. It allows you to write non-blocking, concurrent code that's easy to understand and maintain.

## Installation

Add the gem to your project:

~~~ bash
$ bundle add async
~~~

## Core Concepts

`async` has several core concepts:

- A {ruby Async::Task} instance which captures your sequential computations.
- A {ruby Async::Reactor} instance which implements the fiber scheduler interface and event loop.
- A {ruby Fiber} is an object which executes user code with cooperative concurrency, i.e. you can transfer execution from one fiber to another and back again.

### What is a scheduler?

A scheduler is an interface which manages the execution of fibers. It is responsible for intercepting blocking operations and redirecting them to an event loop.

### What is an event loop?

An event loop is part of the implementation of a scheduler which is responsible for waiting for events to occur, and waking up fibers when they are ready to run.

### What is a selector?

A selector is part of the implementation of an event loop which is responsible for interacting with the operating system and waiting for specific events to occur. This is often referred to as "select"ing ready events from a set of file descriptors, but in practice has expanded to encompass a wide range of blocking operations.

### What is a reactor?

A reactor is a specific implementation of the scheduler interface, which includes an event loop and selector, and is responsible for managing the execution of fibers.

## Creating an Asynchronous Task

The main entry point for creating tasks is the {ruby Kernel#Async} method. Because this method is defined on `Kernel`, it's available in all parts of your program.

~~~ ruby
require 'async'

Async do |task|
	puts "Hello World!"
end
~~~

A {ruby Async::Task} runs using a {ruby Fiber} and blocking operations e.g. `sleep`, `read`, `write` yield control until the operation can complete. When a blocking operation yields control, it means another fiber can execute, giving the illusion of simultaneous execution.

### When should I use `Async`?

You should use `Async` when you desire explicit concurrency in your program. That means you want to run multiple tasks at the same time, and you want to be able to wait for the results of those tasks.

- You should use `Async` when you want to perform network operations concurrently, such as HTTP requests or database queries.
- You should use `Async` when you want to process independent requests concurrently, such as a web server.
- You should use `Async` when you want to handle multiple connections concurrently, such as a chat server.

You should consider the boundary around your program and the request handling. For example, one task per operation, request or connection, is usually appropriate.

### Waiting for Results

Similar to a promise, {ruby Async::Task} produces results. In order to wait for these results, you must invoke {ruby Async::Task#wait}:

``` ruby
require "async"

task = Async do
	rand
end

puts "The number was: #{task.wait}"
```

## Creating a Fiber Scheduler

The first (top level) async block will also create an instance of {ruby Async::Reactor} which is a subclass of {ruby Async::Scheduler} to handle the event loop. You can also do this directly using {ruby Fiber.set_scheduler}:

~~~ ruby
require 'async/scheduler'

scheduler = Async::Scheduler.new
Fiber.set_scheduler(scheduler)

Fiber.schedule do
	1.upto(3) do |i|
		Fiber.schedule do
			sleep 1
			puts "Hello World"
		end
	end
end
~~~

## Synchronous Execution in an existing Fiber Scheduler

Unless you need fan-out, map-reduce style concurrency, you can actually use a slightly more efficient {ruby Kernel::Sync} execution model. This method will run your block in the current event loop if one exists, or create an event loop if not. You can use it for code which uses asynchronous primitives, but itself does not need to be asynchronous with respect to other tasks.

```ruby
require "async/http/internet"

def fetch(url)
	Sync do
		internet = Async::HTTP::Internet.new
		return internet.get(url).read
	end
end

# At the level of your program, this method will create an event loop:
fetch("https://example.com")

Sync do
	# The event loop already exists, and will be reused:
	fetch("https://example.com")
end
```

In other words, `Sync{...}` is very similar in behaviour to `Async{...}.wait`, but significantly more efficient.

## Enforcing Embedded Execution

In some methods, you may want to implement a fan-out or map-reduce. That requires a parent scheduler. There are two ways you can do this:

```ruby
def fetch_all(urls, parent: Async::Task.current)
	urls.map do |url|
		parent.async do
			fetch(url)
		end
	end.map(&:wait)
end
```

or:

```ruby
def fetch_all(urls)
	Sync do |parent|
		urls.map do |url|
			parent.async do
				fetch(url)
			end
		end.map(&:wait)
	end
end
```

The former allows you to inject the parent, which could be a barrier or semaphore, while the latter will create a new parent scheduler if one does not exist. In both cases, you guarantee that the map operation will be executed in the parent task (of some sort).

## Compatibility

The Fiber Scheduler interface is compatible with most pure Ruby code and well-behaved C code. For example, you can use {ruby Net::HTTP} for performing concurrent HTTP requests:

```ruby
urls = ["http://example.com", "http://example.org", "http://example.net"]

Async do
	# Perform several concurrent requests:
	responses = urls.map do |url|
		Async do
			Net::HTTP.get(URI(url))
		end
	end.map(&:wait)
end
```

Unfortunately, some libraries do not integrate well with the fiber scheduler: either they are blocking, processor bound, or use thread locals for execution state. To use these libraries, you may be able to use a background thread.

```ruby
Async do
	result = Thread.new do
		# Code which is otherwise unsafe...
	end.value # Wait for the result of the thread, internally non-blocking.
end
```