Skip to content

Commit e4ef2e0

Browse files
authored
Update AsyncSeq.fsx
1 parent 074ebaf commit e4ef2e0

1 file changed

Lines changed: 18 additions & 28 deletions

File tree

docs/AsyncSeq.fsx

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
1818
# F# Async: FSharp.Control.AsyncSeq
1919
20+
> NOTE: There is also the option to use [FSharp.Control.TaskSeq](https://github.com/fsprojects/FSharp.Control.TaskSeq) which has a very similar usage model.
21+
2022
An AsyncSeq is a sequence in which individual elements are retrieved using an `Async` computation.
2123
It is similar to `seq<'a>` in that subsequent elements are pulled on-demand.
2224
`AsyncSeq` also bears similarity to `IObservable<'a>` with the former being based on an "asynchronous pull" and the
@@ -30,15 +32,13 @@ The `AsyncSeq` type is located in the `FSharp.Control.AsyncSeq.dll` assembly whi
3032
#r "../../../bin/FSharp.Control.AsyncSeq.dll"
3133
open FSharp.Control
3234

33-
34-
3535
(**
3636
### Generating asynchronous sequences
3737
38-
An `AsyncSeq<'a>` can be generated using computation expression syntax much like `seq<'a>`:
38+
An `AsyncSeq<'T>` can be generated using computation expression syntax much like `seq<'T>`:
3939
*)
4040

41-
let asyncS = asyncSeq {
41+
let async12 = asyncSeq {
4242
yield 1
4343
yield 2
4444
}
@@ -123,19 +123,19 @@ composed using familiar operations on sequences. Furthermore, it will be execute
123123
*)
124124

125125
(**
126-
### Comparison with seq<'a>
126+
### Comparison with seq<'T>
127127
128-
The central difference between `seq<'a>` and `AsyncSeq<'a>` can be illustrated by introducing the notion of time.
128+
The central difference between `seq<'T>` and `AsyncSeq<'T>` can be illustrated by introducing the notion of time.
129129
Suppose that generating subsequent elements of a sequence requires an IO-bound operation. Invoking long
130-
running IO-bound operations from within a `seq<'a>` will *block* the thread which calls `MoveNext` on the
130+
running IO-bound operations from within a `seq<'T>` will *block* the thread which calls `MoveNext` on the
131131
corresponding `IEnumerator`. An `AsyncSeq` on the other hand can use facilities provided by the F# `Async` type to make
132132
more efficient use of system resources.
133133
*)
134134

135135
let withTime = seq {
136-
System.Threading.Thread.Sleep(1000) // calling thread will block
136+
Thread.Sleep(1000) // calling thread will block
137137
yield 1
138-
System.Threading.Thread.Sleep(1000) // calling thread will block
138+
Thread.Sleep(1000) // calling thread will block
139139
yield 1
140140
}
141141

@@ -151,22 +151,19 @@ When the asynchronous sequence `withTime'` is iterated, the calls to `Async.Slee
151151
the *continuation* of the sequence will be scheduled by `Async` while the calling thread will be free to perform other work.
152152
Overall, a `seq<'a>` can be viewed as a special case of an `AsyncSeq<'a>` where subsequent elements are retrieved
153153
in a blocking manner.
154-
*)
155154
155+
### Comparison with IObservable<'T>
156156
157-
(**
158-
### Comparison with IObservable<'a>
159-
160-
Both `IObservable<'a>` and `AsyncSeq<'a>` represent collections of items and both provide similar operations
157+
Both `IObservable<'T>` and `AsyncSeq<'T>` represent collections of items and both provide similar operations
161158
for transformation and composition. The central difference between the two is that the former uses a *synchronous push*
162159
to a subscriber and the latter uses an *asynchronous pull* by a consumer.
163-
Consumers of an `IObservable<'a>` *subscribe* to receive notifications about
164-
new items or the end of the sequence. By contrast, consumers of an `AsyncSeq<'a>` *asynchronously retrieve* subsequent items on their own
160+
Consumers of an `IObservable<'T>` *subscribe* to receive notifications about
161+
new items or the end of the sequence. By contrast, consumers of an `AsyncSeq<'T>` *asynchronously retrieve* subsequent items on their own
165162
terms. Some domains are more naturally modeled with one or the other, however it is less clear which is a more
166163
suitable tool for a specific task. In many cases, a combination of the two provides the optimal solution and
167164
restricting yourself to one, while simplifying the programming model, can lead one to view all problems as a nail.
168165
169-
A more specific difference between the two is that `IObservable<'a>` subscribers have the basic type `'a -> unit`
166+
A more specific difference between the two is that `IObservable<'T>` subscribers have the basic type `'T -> unit`
170167
and are therefore inherently synchronous and imperative. The observer can certainly make a blocking call, but this
171168
can defeat the purpose of the observable sequence all together. Alternatively, the observer can spawn an operation, but
172169
this can break composition because one can no longer rely on the observer returning to determine that it has
@@ -177,7 +174,7 @@ To illustrate, let's try to implement the above Tweet retrieval, filtering and s
177174
Suppose we already have an observable sequence representing tweets `IObservable<Tweet>` and we simply wish
178175
to filter it and store the resulting tweets. The function `Observable.filter` allows one to filter observable
179176
sequences based on a predicate, however in this case it doesn't quite cut it because the predicate passed to it must
180-
be synchronous `'a -> bool`:
177+
be synchronous `'T -> bool`:
181178
*)
182179

183180
open System
@@ -190,8 +187,8 @@ let filteredTweetsObs =
190187
|> Observable.filter (filterTweet >> Async.RunSynchronously) // blocking IO-call!
191188

192189
(**
193-
To remedy the blocking IO-call we can better adapt the filtering function to the `IObservable<'a>` model. A value
194-
of type `Async<'a>` can be modeled as an `IObservable<'a>` with one element. Suppose that we have
190+
To remedy the blocking IO-call we can better adapt the filtering function to the `IObservable<'T>` model. A value
191+
of type `Async<'T>` can be modeled as an `IObservable<'T>` with one element. Suppose that we have
195192
`Tweet -> IObservable<bool>`. We can define a few helper operators on observables to allow filtering using
196193
an asynchronous predicate as follows:
197194
*)
@@ -262,13 +259,10 @@ notifications must be pushed immediately without delay.
262259
263260
Upon closer inspection, the consumption approaches between the two models aren't all too different. While `AsyncSeq` is based on an asynchronous-pull operation,
264261
it is usually consumed using an operator such as `AsyncSeq.iterAsync` as shown above. This is a function of type
265-
`('a -> Async<unit>) -> AsyncSeq<'a> -> Async<unit>` where the first argument is a function `'a -> Async<unit>` which performs
262+
`('T -> Async<unit>) -> AsyncSeq<'T> -> Async<unit>` where the first argument is a function `'T -> Async<unit>` which performs
266263
some work on an item of the sequence and is applied repeatedly to subsequent items. In a sense, `iterAsync` *pushes* values to this
267264
function. The primary difference from observers of observable sequences is the return type `Async<unit>` rather than simply `unit`.
268-
*)
269265
270-
271-
(**
272266
### Performance Considerations
273267
274268
While an asynchronous computation obviates the need to block an OS thread for the duration of an operation, it isn't always the case
@@ -277,10 +271,6 @@ non-blocking operation, it simply allows for it. Also of note is that unlike cal
277271
an item from an asynchronous sequence requires several allocations. Usually this is greatly outweighed by the
278272
benefits, it can make a difference in some scenarios.
279273
280-
*)
281-
282-
283-
(**
284274
## Related Articles
285275
286276
* [Programming with F# asynchronous sequences](http://tomasp.net/blog/async-sequences.aspx/)

0 commit comments

Comments
 (0)