sdgfsdh
sdgfsdh

Reputation: 37131

Why does this Async F# code not terminate as expected?

module Async =

  let forever = Async.FromContinuations ignore

  let withTimeout timeout action =
    async {
      let! child = Async.StartChild (action, timeout)
      return! child
    }

async {
  printfn "Started... "

  do!
    Async.forever
    |> Async.withTimeout 1_000

  printfn "Finished. "
}
|> Async.RunSynchronously

Why does this code not terminate? I would expect it to finish after 1000ms.

$ dotnet --version
5.0.103

Upvotes: 2

Views: 88

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243096

In F# async, checking for cancellation is collaborative - this means that the individual primitive asynchronous operations have to "collaborate" with the system and check for cancellation (if they do something that does not automatically propagate the cancellation token).

In other words, in F# async, cancellation checks are done automatically on do! and let! etc., but if you block or never call a continuation, then the checks do not happen.

You could modify your forever to register a cancellation handler and trigger the operation cancelled continuation:

let forever : Async<unit> = async {
  let! tok = Async.CancellationToken
  return! Async.FromContinuations(fun (cont, econt, ccont) ->
    tok.Register(fun _ -> ccont(System.OperationCanceledException("cancelled!"))) 
    |> ignore
  )
}

With this, running your sample prints "Starting" and then it fails with TimeoutException. If you actually wanted to print the "finish" text, you could write:

async {
  printfn "Started... "
  try
    do! Async.forever |> Async.withTimeout 1_000
  finally 
    printfn "Finished. " }
|> Async.RunSynchronously

Upvotes: 4

Related Questions