Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript

Running Flows

Creating a Flow does not execute it. Start it explicitly at a boundary, and read the outcome from the Exit<'value, 'error> it returns:

> let workflow () : Flow<string> = Flow.succeed "Hello";;
> Flow.run () (workflow ());;
> match Flow.run () (workflow ()) with Exit.Success value -> value | Exit.Failure cause -> Cause.prettyPrint string cause;;
val it: Exit<string,Never> = Success "Hello"
val it: string = "Hello"

Flow.run blocks until the Exit is available. Flow.startTask starts the work at once and returns a task to await, and Flow.toAsync returns an async that starts nothing until it is run:

> let mutable started = 0;;
> let counted () : Flow<int> = Flow.delay (fun () -> started <- started + 1; Flow.ok started);;
> let cold = Flow.toAsync () (counted ()) in started;;
> (Flow.startTask () (counted ())).Result;;
> Flow.toAsync () (counted ()) |> Async.RunSynchronously;;
val started: int = 0
val it: int = 0
val it: Exit<int,Never> = Success 1
val it: Exit<int,Never> = Success 2

The name states when work begins. to* builds a description and starts nothing, start* begins execution immediately and hands back a handle, and run executes to completion:

Entry point Starts work?
Flow.run / RunSynchronously Yes, and blocks until the Exit is available
Flow.startTask / StartAsTask / StartAsValueTask Yes: the work is already running when it returns
Flow.toAsync / ToAsync No: nothing runs until the returned async is started

This matters when you build a handle without awaiting it. StartAsTask has already begun the work at that point; ToAsync has not, and discarding the async discards the work.

The members take an optional cancellationToken for interop callers; the module functions take none, which keeps the common path short. Use Flow.timeout to bound how long a workflow may run. On Fable, use ToAsync.

Every call starts a fresh execution with its own root scope. Await the returned handle to receive the final Exit.

Direct execution is useful at interop boundaries. A complete application normally starts its root workflow with App.run, introduced at the end of this section.

Go Further