以下测试显示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 _ -> ()
)
)
}
答案 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
和其他错误。