Asynchronous 为什么不能立即取消Async.Sleep?

Asynchronous 为什么不能立即取消Async.Sleep?,asynchronous,f#,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 () -

下面的测试表明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 _ -> ()
            )
    )
}
静态成员SleepEx(毫秒:int)=异步{
让disp=new SerialDisposable()
使用!ch=Async.OnCancel(fun()->disp.Dispose())
do!Async.FromContinuations(乐趣(成功、错误、取消)->
let timerSubscription=new SerialDisposable()
让CompleteWith=
let completed=ref 0
乐趣继续->
如果联锁。交换(完成,1)=0,则
timerSubscription.Dispose()
用->()尝试cont()
一次性用品
CompleteWith(fun()->cancel(新操作CanceledException()))
)
设tmr=新计时器(
回调=(乐趣状态->完成(成功)),
state=null,dueTime=毫秒,period=Timeout.Infinite
)
如果tmr=null,则
CompleteWith(fun()->错误(新异常(“未能创建计时器”))
其他的
时间订阅。一次性
尝试将tmr.Dispose()与->()
)
)
}

我不会说这是一个bug——它源于F#异步工作流中通常处理取消的方式。通常,F#假设您使用
调用的基本操作让
执行
不支持取消(我想在.NET中没有标准的取消机制),因此F#在使用
let进行呼叫之前和之后插入取消检查

所以一个电话
让我来!res=foo()

当然,由
foo()
返回的工作流可以更好地处理取消-通常,如果它是使用
async{..}
块实现的,那么它将围绕每个
let。但是,通常(除非某些操作以更巧妙的方式实现),取消将在下一次
let调用完成

你的
Sleep
的替代定义在我看来相当不错-它比F#library中提供的定义更支持取消,如果你需要立即取消,那么用你的
SleepEx
替换F#的
Async.Sleep
是唯一的方法。但是,可能仍然会有一些操作不支持immediate取消,因此您可能会在其他地方遇到问题(如果您在任何地方都需要这种行为)

PS:我认为你的
SleepEx
功能对其他人可能非常有用。如果你能在网上分享,那就太棒了

我认为这是不正确的行为。你认为这是一个怎样的错误

是的,我还以为是虫子呢。我把它当作臭虫报告了。微软同意这是一个bug。他们已经修复了F#3.0/VS2012中的错误,以及TryScan和其他程序中的错误。

NB F#>=3的睡眠将正确地执行取消,通过取消执行线程,作为静态成员SleepCorrect(x:TimeSpan)=async{let!ct=async.CancellationToken return!System.threading.Tasks.Task.Delay(x,ct)|>Async.AwaitTaskCorrect}
就可以了
token.ThrowIfCancellationRequested()
let! res = foo()
token.ThrowIfCancellationRequested()