2012-01-27 48 views
7

以下测试显示F#2.0中的Async.Sleep无法立即取消。只有在时间过后,我们才会收到“取消”通知。有没有任何理由为什么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

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

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

5

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

于是打电话let! res = foo()其实更像是以下(虽然检查隐藏在库实现的async):

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

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

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

PS:我觉得你的SleepEx功能可能对他人非常有用。如果你可以在F# Snippets web site上分享,那太棒了!

相关问题