johv
johv

Reputation: 4594

When does f# async check its CancellationToken?

I'm reading F# for fun and profit - Asynchronous programming. Under Cancelling workflows they have the following example:

let testLoop = async {
    for i in [1..100] do
    // do something
    printf "%i before.." i
    
    // sleep a bit 
    do! Async.Sleep 10  
    printfn "..after"
    }

open System
open System.Threading

// create a cancellation source
let cancellationSource = new CancellationTokenSource()

// start the task, but this time pass in a cancellation token
Async.Start (testLoop,cancellationSource.Token)

// wait a bit
Thread.Sleep(200)  

// cancel after 200ms
cancellationSource.Cancel()

About this they say:

In F#, any nested async call will check the cancellation token automatically!

In this case it was the line:

do! Async.Sleep(10) 

As you can see from the output, this line is where the cancellation happened.

However, for me (VS2010, F# 2.0, F# Interactive) I get the following output. Notice how it also prints ..after after I have canceled the token. Are they simply wrong?

1 before....after
2 before....after
3 before....after
4 before....after
5 before....after
6 before....after
7 before....after
8 before....after
9 before....after
10 before....after
11 before....after
12 before....after
13 before..
val cancellationSource : CancellationTokenSource

>
..after

So perhaps the check for cancellation is done when entering Async.Sleep? No, then it would've printed:

13 before....after
14 before..
val cancellationSource : CancellationTokenSource

>

So it seems like the check is actually in the for-loop! I.e. it keeps running until the for loop after being cancelled. Is this how it works? What then if I'd rather want it to check after the sleep?

This question seems to hint at cancellation working like I described above: Can I explicitly check for cancellation / terminate async computation?

Edit: Regarding if this is only in FSI 2.0: What happens with the following loop, if one sleep 200ms, 2500ms and 4000ms respectively? Does it print middle?

let testLoop = async {
    for i in [1..5] do
    printf "%i before.." i
    do! Async.Sleep 2000
    printfn "..middle.."
    do! Async.Sleep 1000  
    printfn "..after"
    }

Upvotes: 11

Views: 2317

Answers (1)

Leaf Garland
Leaf Garland

Reputation: 3697

I see the same results as you with F# 2.0 only in interactive Fsi. If I put the same code in a file and run fsi cancel.fsx, then the output does not have the final after and is what you expect.

Fsi v11 and v12 show the expected output for both ways of running the code.

This suggests there is some bug or difference when running Fsi v2.0 interactively that was fixed in a later version of FSharp.

Upvotes: 2

Related Questions