【问题标题】:F# Async.RunSynchronously with timeout and cancellationTokenF# Async.RunSynchronously 与 timeout 和 cancelToken
【发布时间】:2013-01-21 13:04:00
【问题描述】:

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

这是预期的行为吗?有什么办法可以得到我所追求的行为吗?

谢谢!

【问题讨论】:

    标签: f# cancellation-token async-workflow


    【解决方案1】:

    我不确定这是否是预期的行为 - 至少,我看不出有任何原因。但是,这种行为是在处理RunSynchronously 的参数时直接实现的。如果你看library source code,你可以看到:

    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()
    

    【讨论】:

    • PS:我向 F# 团队报告了这个问题(如果发现其他问题,可以使用 fsbugs at microsoft dot com)
    • 我不确定这是什么时候添加的,但msdn.microsoft.com/en-us/library/ee370262.aspx 的文档说If you provide a cancelable cancellation token, the timeout is ignored
    • @TomasPetricek 您是否有指向该旧报告的链接?顺便说一句,今天我问了一个与此类似的问题,但没有同步部分:stackoverflow.com/questions/54978550/…
    猜你喜欢
    • 2019-07-25
    • 2014-06-21
    • 2014-04-23
    • 1970-01-01
    • 1970-01-01
    • 2013-10-24
    • 2012-03-24
    • 2021-08-27
    • 2013-10-13
    相关资源
    最近更新 更多