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

and*_*.ko 8 f# asynchronous

以下测试显示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
Run Code Online (Sandbox Code Playgroud)

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

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 _ -> ()
            )
    )
}
Run Code Online (Sandbox Code Playgroud)

Tom*_*cek 6

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

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

token.ThrowIfCancellationRequested()
let! res = foo()
token.ThrowIfCancellationRequested()
Run Code Online (Sandbox Code Playgroud)

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

你选择的定义Sleep看起来很不错,我-它支持取消比F#库中提供一个更好的,如果你需要立即取消,然后替换F#的Async.SleepSleepEx是唯一的出路.但是,可能仍会有一些操作不支持免费取消,因此您可能会在其他地方遇到问题(如果您在任何地方都需要这种行为).

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


Jon*_*rop 3

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

是的,我认为这是一个错误。我将其报告为错误。微软承认这是一个错误。他们修复了 F# 3.0 / VS2012 中的错误以及TryScan其他错误。