This is an example of how to run asynchronous tasks, wait for all tasks to complete and then exit the process. There are many ways to achieve this but the snippet below is my personal favourite.

open System
open System.Diagnostics
open System.Collections.Generic
open System.Threading.Tasks

[<EntryPoint>]
let main argv =
    let r = Random()
    let makeTask id =
        task {
            let wait = 1000 + (r.Next(5) * 1000)
            do! Async.Sleep wait
            printfn $"Task {id} executed in {wait}s." 
        }
    let tasks = List<Task>()
    for i in 1 .. 10 do
        tasks.Add(makeTask(i))
    Task.WhenAll(tasks.ToArray()).ContinueWith(fun _ -> Process.GetCurrentProcess().Kill()) |> ignore
    Process.GetCurrentProcess().WaitForExit()
    0

Let’s go through the code line by line.

The open code loads libraries from C#.

We will need System.Diagnostics to access the process itself. If we removed everything and kept only:

Process.GetCurrentProcess().WaitForExit()

the process would run forever and still not exit.

Next we are defining an asynchronous Task. This is the new way of doing asynchronous available as from F# 6. Inside the task we are waiting for a random amount time and then printing that the task has executed in a number of seconds.

Next we are creating a List type taken from c# open System.Collections.Generic. This is a mutable list in which we will add 10 Tasks to, each with an identifier i.

We then use Task.WhenAll(tasks.ToArray()) to run all the tasks in list in parallel. Using the .ContinueWith we can chain to execute another task at the end of the execution of all the 10 Tasks.

Finally, we tell the process to Kill itself after executing the 10 Tasks. This is one way of keeping an application running without an infinite for loop.

I have to put a disclaimer here that I am still learning F# and there might be better ways to achieve this.