簡體   English   中英

F#Async.Run與timeout和cancellationToken同步

[英]F# Async.RunSynchronously with timeout and cancellationToken

使用超時和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.

這是預期的行為嗎? 有沒有辦法得到我追求的行為?

謝謝!

我不確定這是否是預期的行為 - 至少,我認為沒有任何理由。 但是,此行為直接在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()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM