A brief history of async/await
Tags: csharp, fsharp, programming, dotnet
Published: 2022-10-22 21:30:28

The async/await pattern is a syntactic feature of many programming languages that allows an asynchronous, non-blocking function to be structured similarly to an ordinary synchronous function. It is semantically related to the concept of a coroutine and is often implemented using similar techniques.

Perhaps, the first mainstream language that adopted this feature was C#. During the next decade, the feature spread across many other languages:
- 2012 — C# 5  
- 2015 — Python 3.5, TypeScript 1.7
- 2017 — ECMAScript 2017
- 2018 — Kotlin 1.3
- 2019 — Rust 
- 2020 — C++ 20
- 2021 — Swift

That being said, in 2022 one can state `async/await`, implemented one way or another, is an industry standard. Despite that C# was the first *mainstream* language that popularised the `async/await` keywords, it wasn’t the language that invented the concept. F# added asynchronous workflows with await points in version 2.0 in 2007 (5 years before C#). 

It’s interesting that with [version 6 release](https://learn.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-6) in 2021 (14 years later) the recommended async pattern in F# was changed from `async` to `task`. At first glance, these two pieces of code look almost identical:
```fsharp
// async
let readFilesTask (path1, path2) =
   async {
        let! bytes1 = File.ReadAllBytesAsync(path1) |> Async.AwaitTask
        let! bytes2 = File.ReadAllBytesAsync(path2) |> Async.AwaitTask
        return Array.append bytes1 bytes2
   } |> Async.StartAsTask

// task
let readFilesTask (path1, path2) =
   task {
        let! bytes1 = File.ReadAllBytesAsync(path1)
        let! bytes2 = File.ReadAllBytesAsync(path2)
        return Array.append bytes1 bytes2
   }
```

Ironically, the new recommended way is less _functional_ (in the sense of functional programming) than the original one. Consider the following code:
```fsharp
let a = async {
   printfn "async"
}
let seq = seq {a; a}

printfn "Begin"
seq |> Seq.iter Async.RunSynchronously
printfn "End"
```

It will print:
```
Begin
async
async
End
```
If we removed `seq |> Seq.iter Async.RunSynchronously` then the output would be just:
```
Begin
End
```

In other words, `Async` is lazy by default. It will not start unless the code is awaited. `Async` will not cache the result as well. If we changed this code in favour of using tasks:

```fsharp
let t = task {
   printfn "task"
}

let seq = seq {t; t}

printfn "Begin"
seq |> Seq.iter  (Async.AwaitTask >> Async.RunSynchronously)
printfn "End"
```

We would see:
```
task
Begin
End
```

Not only tasks start as soon as they are created. Tasks' results are also cached. The `await` or `let!` keywords would check `Task.IsCompleted` property. When `true`, tasks are executed synchronously because the result is already cached in the `Task.Result` property.

Why does it matter? I highly recommend reading [Async as surrogate IO](https://blog.ploeh.dk/2016/04/11/async-as-surrogate-io/) by Mark Seemann and its comments. In a nutshell, I believe that async/await got a lot of inspiration from the [Haskel IO](https://www.haskell.org/tutorial/io.html) monad which was released in Haskell 98. I'm not claiming that F# `Async` was a direct translation of Haskell's IO, but there are similarities. In this sense, `Async` has a much more in common with its monadic predecessor than `Task`.

In the end, F#, the language that brought academic concepts to the object-oriented world, adopted the implementation from C#. The `task` [computation expression](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions) works on top of [TPL](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl) library, so it has better interoperability with Task-based .NET async pattern. Practically speaking, F# fixed discrepancies between the platform and the language syntax. Every other language from the list above also tends to represent the `async/await` syntax by futures/promises or similar data structures. Referential transparency is not guaranteed in any of these implementations though.

- P.S. Haskell lead developer Simon Marlow created the [async package](https://hackage.haskell.org/package/async-2.2.4/docs/Control-Concurrent-Async.html) in 2012, the same year when async/await was introduced in C#.
- P.P.S. It’s possible to [abuse async/await syntax](https://github.com/0x1000000/AsyncMonads/blob/master/AsyncMonads/Maybe.cs) to build monad comprehensions in C#.