Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is there any reason why Async.Sleep can not be canceled immediately?

The following test shows that Async.Sleep in F# 2.0 can not be canceled immediately. We will get 'cancel' notification only after the time will elapsed.

module async_sleep_test
    open System
    open System.Threading
    open System.Threading.Tasks
    open System.Xml

    let cts = new CancellationTokenSource()
    Task.Factory.StartNew(fun () -> 
        try
            Async.RunSynchronously(async{
                printfn "going to sleep"
                do! Async.Sleep(10000)
            }, -1(*no timeout*), cts.Token)
            printfn "sleep completed"
        with 
        | :? OperationCanceledException ->
            printfn "sleep aborted" // we will see it only after 10 sec.
        | _ ->
            printfn "sleep raised error"
    ) |> ignore
    Thread.Sleep(100) // give time to the task to enter in sleep
    cts.Cancel()
    Thread.Sleep(100) // give chance to the task to complete before print bye message
    printfn "press any key to exit...."
    Console.ReadKey(true) |> ignore

I think this is incorrect behavior. How do you think is this a bug? Will be there any surprises if for example I will use the following implementation:

static member SleepEx(milliseconds:int) = async{
    let disp = new SerialDisposable()
    use! ch = Async.OnCancel(fun()->disp.Dispose())
    do! Async.FromContinuations(fun (success, error, cancel) ->
        let timerSubscription = new SerialDisposable()
        let CompleteWith = 
            let completed = ref 0
            fun cont ->
                if Interlocked.Exchange(completed, 1) = 0 then
                    timerSubscription.Dispose()
                    try cont() with _->()

        disp.Disposable <- Disposable.Create(fun()->
            CompleteWith (fun ()-> cancel(new OperationCanceledException()))
        )
        let tmr = new Timer(
            callback = (fun state -> CompleteWith(success)), 
            state = null, dueTime = milliseconds, period = Timeout.Infinite
        )
        if tmr = null then
            CompleteWith(fun ()->error(new Exception("failed to create timer")))
        else
            timerSubscription.Disposable <- Disposable.Create(fun()->
                try tmr.Dispose() with _ -> ()
            )
    )
}
like image 599
andrey.ko Avatar asked Jan 27 '12 23:01

andrey.ko


People also ask

Is Task delay better than thread sleep?

sleep will block a thread and task. delay will not and has a cancellation token, unless your app is pretty complex, it really doesn't matter as on the surface: task. delay and thread. sleep do pretty much the same thing.

Is thread sleep asynchronous?

It's fully async. And on top of that, virtual threads are super cheap. You could create hundreds of thousands of them without overhead or limitations.


2 Answers

I wouldn't say this is a bug - it follows from the way cancellation is handled in F# asynchronous workflows in general. Generally, F# assumes that primitive operations that you call using let! or do! do not support cancellation (I guess there is no standard mechanism for that in .NET), so F# inserts cancellation checks before and after the call made using let!.

So a call let! res = foo() is actually more like the following (although the checks are hidden in the library implementation of async):

token.ThrowIfCancellationRequested()
let! res = foo()
token.ThrowIfCancellationRequested()

Of course, the workflow returned by foo() can handle cancellation better - typically, if it is implemented using an async { .. } block, then it will contain more checks around each let!. However, in general (unless some operation is implemented in a more clever way), the cancellation will be performed after the next let! call completes.

Your alternative definition of Sleep looks pretty good to me - it supports cancellation better than the one available in F# library and if you need immediate cancellation, then replacing F#'s Async.Sleep with your SleepEx is the only way to go. However, there are probably still going to be some operations that do not support immmediate cancellation, so you may run into problems elsewhere (if you need this behavior everywhere).

PS: I think your SleepEx function could be pretty useful for others. If you could share it on the F# Snippets web site, that would be fantastic!

like image 134
Tomas Petricek Avatar answered Sep 22 '22 08:09

Tomas Petricek


I think this is incorrect behavior. How do you think is this a bug?

Yes, I thought it was a bug. I reported it as a bug. Microsoft agreed it was a bug. They've fixed the bug in F# 3.0 / VS2012 along with bugs in TryScan and others.

like image 34
J D Avatar answered Sep 23 '22 08:09

J D