·Publikoval Frank Hliva·3 min čítania

F# - async and compatibility with C#

F# - async and compatibility with C#
F# logo

The F# language is a pioneer in introducing new paradigms and techniques, and that was exactly the case with asynchronous code. While C# introduced async / await only in version 5.0 (June 2013), F# had syntactic sugar for asynchronous programming since version 2.0 (2010), being the very first Microsoft language to do so. It was built on computation expressions. It’s clear that Don Syme does amazing work. Although, paradoxically, I like C#‘s async implementation slightly better in some minor aspects. Let me show you with some examples:

An example of async / await in C#:

async void printGoogleHtmlToConsole()
{
	var webClient = new System.Net.WebClient();
	var html = await webClient.DownloadStringTaskAsync("https://www.google.com/");
	Console.WriteLine("Google HTML:", html);
}

An example of async computation expressions in F#:

let printGoogleHtmlToConsole () = async {
	let webClient = System.Net.WebClient()
	let! html = webClient.AsyncDownloadString "https://www.google.com/"
	printfn "Google HTML: %s" html }

As you can see, instead of await, F# uses the keyword let! (let binding with an exclamation mark). C# built its async / await implementation in its own way, based on Tasks, specifically Task<'T> and Task types (for empty results).

But since C# is the mainstream language and the majority of .NET code is written in C#, many libraries adapt specifically to it. As a result, a lot of asynchronous code only supports Tasks, and F# is sometimes overlooked. This also leads to duplicates, e.g., webClient.DownloadStringTaskAsync for C# returning Task<string> and webClient.AsyncDownloadString for F# returning Async<string>.

Unfortunately, F# must maintain backward compatibility, so it primarily works with the Async<'T> type, which is incompatible with Task<'T>. Of course, F# developers thought about compatibility with Tasks and provided the Async.AwaitTask function, which converts Task<'T> to Async<'T> and Task to Async<unit>.

Here is an example of its usage. Suppose we want to await a C# method WebClient.DownloadStringTaskAsync, which returns Task<'T>:

let printGoogleHtmlToConsole () = async {
	let webClient = System.Net.WebClient()
	let! html = (webClient.DownloadStringTaskAsync "https://www.google.com/" |> Async.AwaitTask)
	printfn "Google HTML: %s" html }

To get Async<'T> from the Task, we called the Async.AwaitTask conversion function, and then we work with the asynchronous result in the classic F# way. Don’t like this code? Me neither. And being a lazy programmer who doesn’t want to type Async.AwaitTask constantly, I wondered why Don Syme couldn’t simply extend AsyncBuilder for Tasks? Then it occurred to me – why not extend it myself? F# is so flexible that you can easily extend it with new language features… So I created an extension of AsyncBuilder, which adds Tasks straight into F# async computation expressions. It’s just a few lines of code, but the payoff is massive.

And here is the original code rewritten after extending AsyncBuilder:

let printGoogleHtmlToConsole () = async {
	let webClient = System.Net.WebClient()
	let! html = webClient.DownloadStringTaskAsync "https://www.google.com/"
	printfn "Google HTML: %s" html }

Wow. I added this extension to my custom web framework Deep, so everyone using it gets Task compatibility by default. The rest of you can add it manually, as the license permits.

Share:
Späť na blog

Súvisiace čítanie

Ďalšie články z blogu

Zobraziť všetky články
I Love My McDonald's

I Love My McDonald's

Today I was reading through the archive of a very old Slovak newspaper from 1914. It was called Slovenské Noviny and was published in Budapest – few people today realize that more Slovaks lived in Budapest at that time than in the German-speaking Pressburg (Bratislava).

Čítať článok