是否有任何理由不能立即取消Async.Sleep?

时间:2012-01-27 23:35:10

标签: asynchronous f#

以下测试显示F#2.0中的Async.Sleep无法立即取消。只有在时间过后,我们才会收到“取消”通知。

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

我认为这是不正确的行为。您如何看待这是一个错误?如果我将使用以下实现,那么会有任何意外:

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 _ -> ()
            )
    )
}

2 个答案:

答案 0 :(得分:6)

我不会说这是一个错误 - 它通常是在F#异步工作流中处理取消的方式。通常,F#假定您使用let!do!调用的原始操作不支持取消(我猜在.NET中没有标准机制),因此F#在之前和之后插入取消检查使用let!拨打电话。

所以调用let! res = foo()实际上更像是以下(尽管检查隐藏在async的库实现中):

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

当然,foo()返回的工作流程可以更好地处理取消 - 通常,如果使用async { .. }块实现取消,则它将在每个let!周围包含更多检查。但是,一般情况下(除非某些操作以更聪明的方式实施),否则将在下一次let!呼叫完成后执行取消。

Sleep的替代定义对我来说非常好 - 它支持取消比F#库中可用的更好,如果您需要立即取消,那么用您的{{1}替换F#的Async.Sleep是唯一的出路。但是,可能仍会有一些操作不支持免费取消,因此您可能在其他地方遇到问题(如果您在任何地方都需要这种行为)。

PS:我认为你的SleepEx功能对其他人来说非常有用。如果您可以在F# Snippets web site上分享,那就太棒了!

答案 1 :(得分:3)

  

我认为这是不正确的行为。您认为这是一个错误吗?

是的,我认为这是一个错误。我把它报告为一个bug。微软同意这是一个错误。他们修复了F#3.0 / VS2012中的错误以及TryScan和其他错误。