Skip to content

Commit 17692d9

Browse files
authored
feat:Async (#35)
* feat:async section * fix:header indentation and missing link --------- Co-authored-by: ⚙︎ Greg
1 parent b417c70 commit 17692d9

File tree

1 file changed

+129
-20
lines changed

1 file changed

+129
-20
lines changed

docs/fsharp-cheatsheet.md

+129-20
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,27 @@ If you have any comments, corrections, or suggested additions, please open an is
66

77
Contents
88
--------
9-
- [Comments](#comments)
10-
- [Strings](#strings)
11-
- [Basic Types and Literals](#basic-types-and-literals)
12-
- [Functions](#functions)
13-
- [Collections](#collections)
14-
- [Lists](#collections-lists)
15-
- [Arrays](#collections-arrays)
16-
- [Sequences](#collections-sequences)
17-
- [Data Types](#data-types)
18-
- [Tuples](#data-types-tuples)
19-
- [Records](#data-types-records)
20-
- [Anonymous Records](#data-types-anonymous-records)
21-
- [Discriminated Unions](#data-types-discriminated-unions)
22-
- [Pattern Matching](#pattern-matching)
23-
- [Exceptions](#exceptions)
24-
- [Classes and Inheritance](#classes-and-inheritance)
25-
- [Interfaces and Object Expressions](#interfaces-and-object-expressions)
26-
- [Active Patterns](#active-patterns)
27-
- [Code Organization](#code-organization)
28-
- [Compiler Directives](#compiler-directives)
9+
* [Comments](#comments)
10+
* [Strings](#strings)
11+
* [Basic Types and Literals](#basic-types-and-literals)
12+
* [Functions](#functions)
13+
* [Collections](#collections)
14+
* [Lists](#collections-lists)
15+
* [Arrays](#collections-arrays)
16+
* [Sequences](#collections-sequences)
17+
* [Data Types](#data-types)
18+
* [Tuples](#data-types-tuples)
19+
* [Records](#data-types-records)
20+
* [Anonymous Records](#data-types-anonymous-records)
21+
* [Discriminated Unions](#data-types-discriminated-unions)
22+
* [Pattern Matching](#pattern-matching)
23+
* [Exceptions](#exceptions)
24+
* [Classes and Inheritance](#classes-and-inheritance)
25+
* [Interfaces and Object Expressions](#interfaces-and-object-expressions)
26+
* [Active Patterns](#active-patterns)
27+
* [Asynchronous Programming](#asynchronous-programming)
28+
* [Code Organization](#code-organization)
29+
* [Compiler Directives](#compiler-directives)
2930

3031
<div id="comments"></div>
3132

@@ -177,6 +178,8 @@ The most common use is when you have a function that receives no parameters, but
177178
let appendSomeTextToFile () = // without unit, only one line would be appended to the file
178179
System.IO.File.AppendAllText($"{__SOURCE_DIRECTORY__}/file.txt", "New line")
179180

181+
<div id="functions-signatures"></div>
182+
180183
### Signatures and Explicit Typing
181184

182185
Function signatures are useful for quickly learning the input and output of functions. The last type is the return type and all preceding types are the input types.
@@ -740,6 +743,112 @@ Another way of implementing interfaces is to use *object expressions*.
740743

741744
*Partial active patterns* share the syntax of parameterized patterns but their active recognizers accept only one argument.
742745

746+
<div id="asynchronous-programming"></div>
747+
748+
## Asynchronous Programming
749+
750+
F# asynchronous programming is centered around two core concepts: async computations and tasks.
751+
752+
async {
753+
do! Async.Sleep (waitInSeconds * 1000)
754+
let! asyncResult = asyncComputation
755+
use! disposableResult = iDisposableAsyncComputation
756+
}
757+
758+
### Async vs Tasks
759+
760+
An async computation is a unit of work, and a task is a promise of a result. A subtle but important distinction.
761+
Async computations are composable and are not started until explicitly requested; Tasks (when created using the `task` expression) are _hot_:
762+
763+
let runAsync waitInSeconds =
764+
async {
765+
printfn "Created Async"
766+
do! Async.Sleep (waitInSeconds * 1000)
767+
printfn $"Completed Async"
768+
}
769+
770+
let runTask waitInSeconds =
771+
task {
772+
printfn "Started Task"
773+
do! System.Threading.Tasks.Task.Delay (waitInSeconds * 1000)
774+
printfn $"Completed Task"
775+
}
776+
777+
let asyncComputation = runAsync 5 // returns Async<unit> and does not print anything
778+
let newTask = runTask 3 // returns System.Threading.Tasks.Task<unit> and outputs: "Started Task" <3 second delay> "Completed Task"
779+
780+
asyncComputation |> Async.RunSynchronously // this now runs the async computation
781+
newTask.Wait() // this will have already completed by this point
782+
783+
// Output:
784+
// Started Task
785+
// Created Async
786+
// Completed Task
787+
// Completed Async
788+
789+
### Async and Task Interop
790+
791+
As F# sits in .NET, and a lot of the codebase uses the C# async/await, the majority of actions are going to be executed and tracked using `System.Threading.Tasks.Task<'T>`s.
792+
793+
#### Async.AwaitTask
794+
795+
This converts a Task into an async computation. It has the [signature](#functions-signatures): `Task<'T>` -> `Async<'T>`
796+
797+
async {
798+
let! bytes = File.ReadAllBytesAsync(path) |> Async.AwaitTask
799+
let fileName = Path.GetFileName(path)
800+
printfn $"File {fileName} has %d{bytes.Length} bytes"
801+
}
802+
803+
#### Async.StartAsTask
804+
805+
This converts an async computation into a Task. It has the [signature](#functions-signatures): `Async<'T>` -> `Task<'T>`
806+
807+
async {
808+
do! Async.Sleep 5000
809+
} |> Async.StartAsTask
810+
811+
### Async and the ThreadPool
812+
813+
Below is a demonstration of the different ways to start an async computation and where it ends up in the dotnet runtime.
814+
In the comments; `M`, `X`, `Y`, and `Z` are used to represent differences in values.
815+
816+
#r "nuget:Nito.AsyncEx, 5.1.2"
817+
818+
let asyncTask from =
819+
async {
820+
printfn $"{from} Thread Id = {System.Threading.Thread.CurrentThread.ManagedThreadId}"
821+
}
822+
823+
let run () =
824+
printfn $"run() Thread Id = {System.Threading.Thread.CurrentThread.ManagedThreadId}"
825+
asyncTask "StartImmediate" |> Async.StartImmediate // run and wait on same thread
826+
asyncTask "Start" |> Async.Start // queue in ThreadPool and do not wait
827+
asyncTask "RunSynchronously" |> Async.RunSynchronously // run and wait; depends
828+
printfn ""
829+
830+
run ()
831+
// run() Thread Id = M - Main, non-ThreadPool thread
832+
// StartImmediate Thread Id = M - started, waited, and completed on run() thread
833+
// Start Thread Id = X - queued and completed in ThreadPool
834+
// RunSynchronously Thread Id = Y - started, waited, and completed in ThreadPool
835+
// Important: As `Start` is queued in the ThreadPool, it might finish before `RunSynchronously` and they will share the same number
836+
837+
// Run in ThreadPool
838+
async { run () } |> Async.RunSynchronously
839+
// run() Thread Id = X - ThreadPool thread
840+
// StartImmediate Thread Id = X - started, waited, and completed on run() thread
841+
// Start Thread Id = Y - queued and completed in new ThreadPool thread
842+
// RunSynchronously Thread Id = X - started, waited, and completed on run() thread
843+
// Important: `RunSynchronously` behaves like `StartImmediate` when on a ThreadPool thread without a SynchronizationContext
844+
845+
// Run in ThreadPool with a SynchronizationContext
846+
async { Nito.AsyncEx.AsyncContext.Run run } |> Async.RunSynchronously
847+
// run() Thread Id = X - ThreadPool thread
848+
// StartImmediate Thread Id = X - started, waited, and completed on run() thread
849+
// Start Thread Id = Y - queued and completed in new ThreadPool thread
850+
// RunSynchronously Thread Id = Z - started, waited, and completed in new ThreadPool thread
851+
743852
<div id="code-organization"></div>
744853

745854
## Code Organization

0 commit comments

Comments
 (0)