TP

# Asynchronous C# and F# (III.): How does it work?

Some time ago, I started writing a series about the differences between the asynchronous model in F# (in Visual Studio 2010) and the asynchronous language extensions for C# proposed at PDC 2010. In the first article, I showed how both of the language features look and I highlighted a couple of differences. In the second article, I discussed the support for cancellation (available only in F#) and how the two models differ semantically (i.e. what are differences in the behaviour). However, I didn't talk about more techincal differences and, in particular, how is the asynchronous code compiled. We'll look at this topic today...

Although the C# asynchronous programming model is very similar to F# asynchronous workflows, the compilation looks quite different. The C# compiler uses a similar technique as when compiling iterators and creates a state machine, while the F# compiler uses an approach based on higher order functions (combinators) that's shared with (most) other computation expressions.

I won't discuss the syntax of F# asynchronous workflows or the C# asynchronous extensions in this article, so if you're new to one of these, here are links to other articles in this series:

Let's start by looking at the mechanism used in the C# compiler. If you already know how iterators work in C# 2.0, then you'll find it quite easy to understand. If you're not familiar with iterators, then don't worry - I'll make the explanation self-contained.

## State-machine async compilation in C#

To explain the compilation, I'll use slightly simplified version of code that downloads the contents of a web page from the first article. I'll omit disposal using the using construct and also exception handling, so that I can show you (roughly) what the C# compiler generates in a few lines of code that can be explained. If you want to see the translation in its full beauty, then you can use Reflector. We'll look at the compilation of the following snippet:

var request = HttpWebRequest.Create(url);
var response = await request.GetResponseAsync();
var stream = response.GetResponseStream();

do {
count = await stream.ReadAsync(buffer, 0, buffer.Length);
temp.Write(buffer, 0, count);
} while (count > 0);

temp.Seek(0, SeekOrigin.Begin);
// (Some code at the end of the method omitted)
return Tuple.Create(title, html.Length);


Leaving synchronous code aside, the snippet starts by asynchronously getting HTTP response (see the first await keyword). Then it enters a loop in which it asynchronously reads data from a stream (second await point) and then synchronously writes the data to a memory stream. At the end of the loop, it does some (synchronous) processing of the downloaded data.

The C# compiler turns the above method into a class. All local variables of the method are turned into fileds of the class (I also added underscore to the name). This is needed, because when an asynchronous operation is started, the method that starts it returns, so all state needs to be stored on heap (in private fields).

The body of the method is divided into blocks where each block runs some synchronous code and then starts asynchronous operation. The operation is started in the background and the method that starts it returns immediately after starting it. (After returning, the current thread can do some other work, such as run another asynchronous computation, perform some GUI update etc.) The individual blocks are all compiled into a single method named MoveNext that contains a big switch that runs the next block (which is stored in a private field _state):

public void MoveNext() {
switch (this._state) {
case Initial:
// State when the method is called - start getting response
this._request = HttpWebRequest.Create(this._url);
this._awaitResponse = request.GetResponseAsync();
this._awaitResponse.TrySetContinuation(this.MoveNextDelegate);
this._state = GotResponse;
break;

case GotResponse:
// Got response - obtain the stream and start the loop
this._response = this._awaitResponse.GetResult<WebResponse>();
this._stream = this._response.GetResponseStream();
goto case LoopBodyStart:

case LoopBodyStart:
// Loop body - start reading next chunk of data to a buffer
break;

this._temp.Write(this._buffer, 0, this._count);
// If the loop has not completed, go to the beginning
if (this._count < 0) goto case LoopBodyStart;

this._temp.Seek(0L, SeekOrigin.Begin);
// (Some code at the end of the method omitted)
this._state = Returned;
this._result = Tuple.Create(this._title, this._html.Length);
}
}


The code is a beautified version of what the C# compiler generates, but the structure of the method is the same. This method is used to generate task that's returned as the result of the async method, but that's not as interesting (you can discover that yourself using Reflector). The interesting aspect is how the code executes:

• At the beginning the state of the object (state machine) is Initial. When an asynchronous method is called, the program actually calls our MoveNext method, which executes the first case of the switch. It (synchronously) creates HTTP web request and then calls the GetResponseAsync method. The result of the call is a Task that starts running in the background.

Immediately after the asynchronous operation is started, the method continues executing. It uses TrySetContinuation to register a handler that will be executed after the background operation completes and it configures the current state to run the next block (GotResponse) when the MoveNext method is called the next time.

• The MoveNextDelegate (set as a continuation in the previous step) refers to the MoveNext method, so when the first asynchronous operation completes, the shown method will be called again. The current state is GotResponse, so it will execute the second block. In the block, it gets the WebResponse value using GetResult and synchronously creates stream. Then it jumps to the start of the loop.

• The body of the loop is first executed from the GotResponse state. Later, it may be executed after the asynchronous operation inside the loop body completes in case when there are still some remaining data.

Inside the body, we start the asynchronous operation ReadAsync, store the returned task in a local field and register a continuation to be called when the operation completes. Then we set the state to LoopAfterEnd, so that the next block is executed after the reading completes.

• In the LoopAfterRead state, we first get the result of the asynchronous operation and then write the data to a local stream. If there are some remaining data then the execution jumps back to the LoopBodyStart case and starts the next asynchronous operation. Otherwise, it processes the data and returns a result (by setting the _result field).

Probably the most important fact about the asynchronous execution described above is that the MoveNext method (which contains all code from our original method translated into a state machine) is executed repeatedly and it never blocks the calling thread. When running asynchronous operation, it schedules the operation and then returns. When the operation completes, it will use the provided continuation to run MoveNext again to perform the next step of the asynchronous computation.

## Combinator based compilation in F#

The way F# asynchronous workflows are compiled is quite different, but one key aspect is the same. The compiler needs to split the whole expression (or method body) into parts between asynchronous calls. This makes it possible to start an asynchronous operation and then release the current thread.

Let's now look at the same example implemented in F#. In the earlier versions of this code snippet, I used a recursive function to implement the looping. This would be translated to a recursive asynchronous workflow. However I thought it would be more interesting to show exactly the same code snippet that uses while loop in F# too. Here is the implementation of the download:

 1: let request = HttpWebRequest.Create(url)
2: let! response = request.AsyncGetResponse()
3: let stream = response.GetResponseStream()
4: while !count > 0 do
5:   let! n = stream.AsyncRead(buffer, 0, buffer.Length)
6:   temp.Write(buffer, 0, n)
7:   count := n
8: temp.Seek(0L, SeekOrigin.Begin) |> ignore
9: (Some code at the end of the method omitted)
10: return title, html.Length F# Web Snippets


The snippet uses a mutable reference cell count to keep the number of bytes returned by the last AsyncRead call. We need to use a mutable value to be able to check it in the termination condition of the while loop. Otherwise, the code is essentially the same as the C# version. It contains two asynchronous operations written using let! and a single while loop. Let's look at the compiled form...

The essential principle of the compilation of asynchronous operations is that the compiler needs to (somehow) make it possible to start the operation in the background and specify the rest of the method as a continuation that can be called when the operation completes. In C#, the continuation is the MoveNext method of some object (where the _state field of the object remembers the current location in the method body). In F#, the continuation is a (lambda) function that directly contains the rest of the body to be executed.

When compiling F# asynchronous workflows, the compiler turns each non-standard operation into a call to some primitive of the computation builder (e.g. async) and transforms the rest of the code into a continuation:

 1: async.Delay(fun () ->
2:   let request = HttpWebRequest.Create(url)
3:   async.Bind(request.AsyncGetResponse(), fun response ->
4:     let stream = response.GetResponseStream()
5:     // Combine two asynchronous computations
6:     async.Combine(
7:       // Build computation representing while loop
8:       async.While( (fun () -> !count > 0),
9:         async.Bind(stream.AsyncRead(buffer, 0, buffer.Length), fun n ->
10:           temp.Write(buffer, 0, n)
11:           count := n
12:           async.Zero() ) ),
13:       // Build (a delayed) computation that follows the loop
14:       async.Delay(fun () ->
15:         temp.Seek(0L, SeekOrigin.Begin) |> ignore
16:         (Some code at the end of the method omitted)
17:         async.Return(title, html.Length) ))))F# Web Snippets


Compared to the state machine compilation done by the C# compiler, the transformation performed by the F# compiler is quite simple. It follows these rules:

• let! bindings are translated to calls to the Bind member. The asynchronous operation is passed as the first argument and a continuation is passed as the second argument.
• while loop containing asynchronous operation is translated to a call to the While member. The first argument is a function that evaluates the termination condition and the second parameter is the body (which can be executed repeatedly).
• Sequence of expressions (e.g. while loop and the expression that follows it) is combined using the Combine member that takes two workflows and returns a single one that runs both of them in a sequence.
• The whole computation is wrapped inside Delay (so that it doesn't start running the first few (synchronous) lines when creating an asynchronous workflow)

The translated version of the code doesn't reveal anything about how F# asynchronous workflows run (when started using Async.Start or other method). The behavior is all encapsulated in the primitive members that are used to build the workflow (such as Bind and While). However, the execution proceeds similarly to the corresponding C# example.

• When the workflow is started, it runs the function passed as an argument to Delay. The function calls AsyncGetResponse to create a workflow representing the operation and passes it to Bind.
• Bind starts the operation and provides it a continuation (our lambda function) that will be called after the operation completes. Then the Bind member returns and so the calling thread can do some other work.
• When the lambda function is called (with response as an argument), it synchronously gets the response stream. Then it constructs a workflow that will be executed next by combining a while loop with consecutive code.
• The workflow returned by the Combine combinators starts the first workflow (constructed by While). It repeatedly runs the body of the loop (which is itself an asynchronous workflow) until the termination condition returns false.
• When the workflow constructed by While completes, the code generated by Combine calls the second workflow, which performs some (synchronous) processing and then constructs & returns a workflow that reports the result (using Return).

## Comparison

The approaches used by C# and F# are quite different and both have pros and cons. The key advantage of the state machine compilation is that it generates more efficient code. However, this is probably not a big concern for asynchronous code, which spends most of the time waiting for some I/O operation or an external event. However, if you used async to write CPU bound code, then there will probably be some difference in the performance. Currently, the F# compiler performs similar translation when compiling sequence expressions (where efficiency depends more on the compilation). I remember talking about the compilation of asynchronous workflows with Don Syme during my second internship (two years ago :-)), so this may eventually be added to F# too.

The key advantage of the compilation based on combinators is that it is very flexible. The implementation is hidden in a library that can be easily changed. This also makes it possible to implement more complicated behavior - for example, the support for cancellation. Moreover, F# asynchronous workflows are just an example of computation expressions, which means that the compiler doesn't know anything about them. The same mechanism can be used to implement a wide range of other interesting computations (e.g. my article about implementing break in F#).

val request : WebRequest

type: WebRequest
implements: System.Runtime.Serialization.ISerializable
inherits: System.MarshalByRefObject
type HttpWebRequest =
class
inherit System.Net.WebRequest
member Abort : unit -> unit
member Accept : string with get, set
member AddRange : int -> unit
member AddRange : int * int -> unit
member AddRange : string * int -> unit
member AddRange : string * int * int -> unit
member AllowAutoRedirect : bool with get, set
member AllowWriteStreamBuffering : bool with get, set
member AutomaticDecompression : System.Net.DecompressionMethods with get, set
member BeginGetRequestStream : System.AsyncCallback * obj -> System.IAsyncResult
member BeginGetResponse : System.AsyncCallback * obj -> System.IAsyncResult
member ClientCertificates : System.Security.Cryptography.X509Certificates.X509CertificateCollection with get, set
member Connection : string with get, set
member ConnectionGroupName : string with get, set
member ContentLength : int64 with get, set
member ContentType : string with get, set
member ContinueDelegate : System.Net.HttpContinueDelegate with get, set
member Credentials : System.Net.ICredentials with get, set
member EndGetRequestStream : System.IAsyncResult -> System.IO.Stream
member EndGetRequestStream : System.IAsyncResult * System.Net.TransportContext -> System.IO.Stream
member EndGetResponse : System.IAsyncResult -> System.Net.WebResponse
member Expect : string with get, set
member GetRequestStream : unit -> System.IO.Stream
member GetRequestStream : System.Net.TransportContext -> System.IO.Stream
member GetResponse : unit -> System.Net.WebResponse
member HaveResponse : bool
member IfModifiedSince : System.DateTime with get, set
member KeepAlive : bool with get, set
member MaximumAutomaticRedirections : int with get, set
member MaximumResponseHeadersLength : int with get, set
member MediaType : string with get, set
member Method : string with get, set
member Pipelined : bool with get, set
member PreAuthenticate : bool with get, set
member ProtocolVersion : System.Version with get, set
member Proxy : System.Net.IWebProxy with get, set
member ReadWriteTimeout : int with get, set
member Referer : string with get, set
member RequestUri : System.Uri
member SendChunked : bool with get, set
member ServicePoint : System.Net.ServicePoint
member Timeout : int with get, set
member TransferEncoding : string with get, set
member UnsafeAuthenticatedConnectionSharing : bool with get, set
member UseDefaultCredentials : bool with get, set
member UserAgent : string with get, set
static member DefaultCachePolicy : System.Net.Cache.RequestCachePolicy with get, set
static member DefaultMaximumErrorResponseLength : int with get, set
static member DefaultMaximumResponseHeadersLength : int with get, set
end

Full name: System.Net.HttpWebRequest

type: HttpWebRequest
implements: System.Runtime.Serialization.ISerializable
inherits: WebRequest
inherits: System.MarshalByRefObject
WebRequest.Create(requestUri: System.Uri) : WebRequest
WebRequest.Create(requestUriString: string) : WebRequest
val url : string

Full name: Untitled.url

type: string
implements: System.IComparable
implements: System.ICloneable
implements: System.IConvertible
implements: System.IComparable<string>
implements: seq<char>
implements: System.Collections.IEnumerable
implements: System.IEquatable<string>
val response : WebResponse

type: WebResponse
implements: System.Runtime.Serialization.ISerializable
implements: System.IDisposable
inherits: System.MarshalByRefObject
member WebRequest.AsyncGetResponse : unit -> Async<WebResponse>
val stream : Stream

type: Stream
implements: System.IDisposable
inherits: System.MarshalByRefObject
WebResponse.GetResponseStream() : Stream
val count : int ref

Full name: Untitled.count

type: int ref
implements: System.Collections.IStructuralEquatable
implements: System.IComparable<Ref<int>>
implements: System.IComparable
implements: System.Collections.IStructuralComparable
val n : int

type: int
implements: System.IComparable
implements: System.IFormattable
implements: System.IConvertible
implements: System.IComparable<int>
implements: System.IEquatable<int>
inherits: System.ValueType
member Stream.AsyncRead : count:int -> Async<byte []>
member Stream.AsyncRead : buffer:byte [] * ?offset:int * ?count:int -> Async<int>
val buffer : 'a []

Full name: Untitled.buffer
val temp : MemoryStream

Full name: Untitled.temp

type: MemoryStream
implements: System.IDisposable
inherits: Stream
inherits: System.MarshalByRefObject
Stream.Write(buffer: byte [], offset: int, count: int) : unit
Stream.Seek(offset: int64, origin: SeekOrigin) : int64
type SeekOrigin =
| Begin = 0
| Current = 1
| End = 2

Full name: System.IO.SeekOrigin

type: SeekOrigin
inherits: System.Enum
inherits: System.ValueType
field SeekOrigin.Begin = 0
val ignore : 'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
let title = regTitle.Match(html).Groups.[1].Value, html.Length
val title : string * int
val html : string

type: string
implements: System.IComparable
implements: System.ICloneable
implements: System.IConvertible
implements: System.IComparable<string>
implements: seq<char>
implements: System.Collections.IEnumerable
implements: System.IEquatable<string>
property System.String.Length: int
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
member AsyncBuilder.Delay : generator:(unit -> Async<'T>) -> Async<'T>
member AsyncBuilder.Bind : computation:Async<'T> * binder:('T -> Async<'U>) -> Async<'U>
member AsyncBuilder.Combine : computation1:Async<unit> * computation2:Async<'T> -> Async<'T>
member AsyncBuilder.While : guard:(unit -> bool) * computation:Async<unit> -> Async<unit>
member AsyncBuilder.Zero : unit -> Async<unit>
member AsyncBuilder.Return : value:'T -> Async<'T>

Published: Sunday, 21 November 2010, 3:15 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: c#, functional, asynchronous, f#