2012-01-27 5 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 

これは間違った動作だと思います。これはバグだとどのように思いますか?それは、キャンセルは、一般的にF#の非同期ワークフローで処理される方法から、次の -

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

これは誤った動作だと思います。これはバグだとどのように思いますか?

はい、私はバグだと思っていました。私はバグとして報告しました。マイクロソフトはバグだと同意した。彼らはTryScanなどのバグと共に、F#3.0/VS2012のバグを修正しました。

5

が、私はこれはバグでは言わないだろう。例えば、私は、次の実装を使用する場合は任意の驚きがあるでしょう。一般的に、F#では、let!またはdo!を使用して呼び出すプリミティブ演算では、キャンセルがサポートされていないと仮定しています(.NETではそのための標準的なメカニズムはないと思います)ので、F#はlet!を使用して呼び出しの前後に取り消しチェックを挿入します。

のでコールlet! res = foo()は(チェックがasyncのライブラリの実装に隠されているが)より次のように実際にある:もちろん

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

foo()によって返されたワークフローは、より良い取り消しを扱うことができる - 一般的に、 async { .. }ブロックを使用して実装されている場合は、let!の周りにさらに多くのチェックが含まれます。しかし、一般的には(何らかの操作がより巧妙な方法で実装されていない限り)、次のlet!呼び出しの完了後にキャンセルが実行されます。 Sleep

あなたの別の定義は、私にはかなりよさそうだ - それは、F#ライブラリで利用可能なものよりも優れてキャンセルをサポートし、あなたはすぐにキャンセルが必要な場合は、その後、あなたのSleepExでF#のAsync.Sleepを交換すると行くための唯一の方法です。しかし、まだまだ解除をサポートしていない操作が残っている可能性があるので、どこかで問題が発生する可能性があります(どこでもこの動作が必要な場合)。

PS:あなたのSleepEx機能は他の人にとっては非常に便利だと思います。あなたがF# Snippets web siteでそれを共有することができれば、それは素晴らしいことでしょう!

関連する問題