Async expressions
This article describes support in F# for async expressions. Async expressions provide one way of performing computations asynchronously, that is, without blocking execution of other work. For example, asynchronous computations can be used to write apps that have UIs that remain responsive to users as the application performs other work. The F# Asynchronous Workflows programming model allows you to write functional programs while hiding the details of thread transition within a library.
Asynchronous code can also be authored using task expressions, which create .NET tasks directly. Using task expressions is preferred when interoperating extensively with .NET libraries that create or consume .NET tasks. When writing most asynchronous code in F#, F# async expressions are preferred because they are more succinct, more compositional, and avoid certain caveats associated with .NET tasks.
Syntax
async { expression }
Remarks
In the previous syntax, the computation represented by expression
is set up to run asynchronously, that is, without blocking the current computation thread when asynchronous sleep operations, I/O, and other asynchronous operations are performed. Asynchronous computations are often started on a background thread while execution continues on the current thread. The type of the expression is Async<'T>
, where 'T
is the type returned by the expression when the return
keyword is used.
The Async
class provides methods that support several scenarios. The general approach is to create Async
objects that represent the computation or computations that you want to run asynchronously, and then start these computations by using one of the triggering functions. The triggering you use depends on whether you want to use the current thread, a background thread, or a .NET task object. For example, to start an async computation on the current thread, you can use Async.StartImmediate
. When you start an async computation from the UI thread, you do not block the main event loop that processes user actions such as keystrokes and mouse activity, so your application remains responsive.
Asynchronous Binding by Using let!
In an async expression, some expressions and operations are synchronous, and some are asynchronous. When you call a method asynchronously, instead of an ordinary let
binding, you use let!
. The effect of let!
is to enable execution to continue on other computations or threads as the computation is being performed. After the right side of the let!
binding returns, the rest of the async expression resumes execution.
The following code shows the difference between let
and let!
. The line of code that uses let
just creates an asynchronous computation as an object that you can run later by using, for example, Async.StartImmediate
or Async.RunSynchronously
. The line of code that uses let!
starts the computation and performs an asynchronous wait: the thread is suspended until the result is available, at which point execution continues.
// let just stores the result as an asynchronous operation.
let (result1 : Async<byte[]>) = stream.AsyncRead(bufferSize)
// let! completes the asynchronous operation and returns the data.
let! (result2 : byte[]) = stream.AsyncRead(bufferSize)
let!
can only be used to await F# async computations Async<T>
directly. You can await other kinds of asynchronous operations indirectly:
- .NET tasks, Task<TResult> and the non-generic Task, by combining with
Async.AwaitTask
- .NET value tasks, ValueTask<TResult> and the non-generic ValueTask, by combining with
.AsTask()
andAsync.AwaitTask
- Any object following the "GetAwaiter" pattern specified in F# RFC FS-1097, by combining with
task { return! expr } |> Async.AwaitTask
.
Control Flow
Async expressions can include control-flow constructs, such as for .. in .. do
, while .. do
, try .. with ..
, try .. finally ..
, if .. then .. else
, and if .. then ..
. These may, in turn, include further async constructs, with the exception of the with
and finally
handlers, which execute synchronously.
F# async expressions don't support asynchronous try .. finally ..
. You can use a task expression for this case.
use
and use!
bindings
Within async expressions, use
bindings can bind to values of type IDisposable. For the latter, the disposal cleanup operation is executed asynchronously.
In addition to let!
, you can use use!
to perform asynchronous bindings. The difference between let!
and use!
is the same as the difference between let
and use
. For use!
, the object is disposed of at the close of the current scope. Note that in the current release of F#, use!
does not allow a value to be initialized to null, even though use
does.
Asynchronous Primitives
A method that performs a single asynchronous task and returns the result is called an asynchronous primitive, and these are designed specifically for use with let!
. Several asynchronous primitives are defined in the F# core library. Two such methods for Web applications are defined in the module FSharp.Control.WebExtensions
: WebRequest.AsyncGetResponse
and WebClient.AsyncDownloadString
. Both primitives download data from a Web page, given a URL. AsyncGetResponse
produces a System.Net.WebResponse
object, and AsyncDownloadString
produces a string that represents the HTML for a Web page.
Several primitives for asynchronous I/O operations are included in the FSharp.Control.CommonExtensions
module. These extension methods of the System.IO.Stream
class are Stream.AsyncRead
and Stream.AsyncWrite
.
You can also write your own asynchronous primitives by defining a function or method whose body is an async expression.
To use asynchronous methods in the .NET Framework that are designed for other asynchronous models with the F# asynchronous programming model, you create a function that returns an F# Async
object. The F# library has functions that make this easy to do.
One example of using async expressions is included here; there are many others in the documentation for the methods of the Async class.
This example shows how to use async expressions to execute code in parallel.
In the following code example, a function fetchAsync
gets the HTML text returned from a Web request. The fetchAsync
function contains an asynchronous block of code. When a binding is made to the result of an asynchronous primitive, in this case AsyncDownloadString
, let!
is used instead of let
.
You use the function Async.RunSynchronously
to execute an asynchronous operation and wait for its result. As an example, you can execute multiple asynchronous operations in parallel by using the Async.Parallel
function together with the Async.RunSynchronously
function. The Async.Parallel
function takes a list of the Async
objects, sets up the code for each Async
task object to run in parallel, and returns an Async
object that represents the parallel computation. Just as for a single operation, you call Async.RunSynchronously
to start the execution.
The runAll
function launches three async expressions in parallel and waits until they have all completed.
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
"MSDN", "http://msdn.microsoft.com/"
"Bing", "http://www.bing.com"
]
let fetchAsync(name, url:string) =
async {
try
let uri = new System.Uri(url)
let webClient = new WebClient()
let! html = webClient.AsyncDownloadString(uri)
printfn "Read %d characters for %s" html.Length name
with
| ex -> printfn "%s" (ex.Message);
}
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
runAll()