5

タイムアウトと CancellationToken を指定して Async.RunSynchronously を呼び出すと、タイムアウト値が無視されるようです。CancellationToken で CancelAfter を呼び出すことでこれを回避できますが、理想的には、ワークフローで発生する例外、TimeOutExceptions と OperationCanceledExceptions を区別できるようにしたいと考えています。

以下のサンプルコードがこれを示していると思います。

open System
open System.Threading

let work = 
    async {
        let endTime = DateTime.UtcNow.AddMilliseconds(100.0)
        while DateTime.UtcNow < endTime do
            do! Async.Sleep(10)
            Console.WriteLine "working..."
        raise ( Exception "worked for more than 100 millis" )
    }


[<EntryPoint>]
let main argv = 
    try
        Async.RunSynchronously(work, 50)
    with
        | e -> Console.WriteLine (e.GetType().Name + ": " + e.Message)

    let cts = new CancellationTokenSource()

    try
        Async.RunSynchronously(work, 50, cts.Token)
    with
        | e -> Console.WriteLine (e.GetType().Name + ": " + e.Message)  


    cts.CancelAfter(80)
    try
        Async.RunSynchronously(work, 50, cts.Token)
    with
        | e -> Console.WriteLine (e.GetType().Name + ": " + e.Message)  

    Console.ReadKey(true) |> ignore

    0

次の出力は、タイムアウトが最初のケース (CancelationToken が指定されていない場合) でのみ有効であることを示しています。

working...
working...
TimeoutException: The operation has timed out.
working...
working...
working...
working...
working...
working...
working...
Exception: worked for more than 100 millis
working...
working...
working...
working...
working...
working...
OperationCanceledException: The operation was canceled.

これは意図した動作ですか?私が求めている動作を取得する方法はありますか?

ありがとう!

4

1 に答える 1

10

これが意図した動作であるかどうかはわかりません。少なくとも、そうなる理由はわかりません。ただし、この動作は のパラメータの処理に直接実装されていますRunSynchronouslyライブラリのソース コードを見ると、次のことがわかります。

static member RunSynchronously (p:Async<'T>,?timeout,?cancellationToken) =
  let timeout,token =
    match cancellationToken with
    | None -> timeout,(!defaultCancellationTokenSource).Token                
    | Some token when not token.CanBeCanceled -> timeout, token                
    | Some token -> None, token

あなたの場合(タイムアウトとキャンセル可能なキャンセルトークンの両方を使用)、コードは最後のブランチを通過し、タイムアウトを無視します。これはバグであるか、ドキュメントに記載する必要があると思います。

回避策として、別の を作成しCancellationTokenSourceてタイムアウトを指定し、それをメインのキャンセル ソースにリンクして、発信者が ( を使用してCreateLinkedTokenSource) 提供できるようにすることができます。を取得するOperationCancelledExceptionと、ソースが実際のキャンセルかタイムアウトかを検出できます。

type Microsoft.FSharp.Control.Async with
  static member RunSynchronouslyEx(a:Async<'T>, timeout:int, cancellationToken) =
    // Create cancellation token that is cancelled after 'timeout'
    let timeoutCts = new CancellationTokenSource()
    timeoutCts.CancelAfter(timeout)

    // Create a combined token that is cancelled either when 
    // 'cancellationToken' is cancelled, or after a timeout
    let combinedCts = 
      CancellationTokenSource.CreateLinkedTokenSource
        (cancellationToken, timeoutCts.Token)

    // Run synchronously with the combined token
    try Async.RunSynchronously(a, cancellationToken = combinedCts.Token)
    with :? OperationCanceledException as e ->
      // If the timeout occurred, then we throw timeout exception instead
      if timeoutCts.IsCancellationRequested then
        raise (new System.TimeoutException())
      else reraise()
于 2013-01-21T13:27:43.157 に答える