F#Async.Run与超时和cancelToken同步 [英] F# Async.RunSynchronously with timeout and cancellationToken

查看:81
本文介绍了F#Async.Run与超时和cancelToken同步的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用超时和CancellationToken调用Async.RunSynchronously时,似乎忽略了超时值。我可以通过在CancellationToken上调用CancelAfter来解决此问题,但理想情况下,我希望能够区分工作流中发生的异常TimeOutExceptions和OperationCanceledExceptions。

When calling Async.RunSynchronously with a timeout and a CancellationToken, the timeout value seems to be ignored. I can work around this by calling CancelAfter on the CancellationToken, but ideally I'd like to be able to distinguish between exceptions that occur in the workflow, TimeOutExceptions and OperationCanceledExceptions.

我相信下面的示例代码演示了这一点。

I believe the sample code below demonstrates this.

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

输出以下内容,显示超时仅在第一种情况下有效(未指定CancellationToken)

The outputs the following, showing that the timeout is only effective in the first case (where no CancelationToken is specified)

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 的参数中实现。如果您查看库源代码,您会看到:

I'm not sure if this is intended behaviour - at least, I do not see any reason why it would be. However, this behaviour is implemented directly in the handling of parameters of RunSynchronously. If you look at the library source code, you can see:

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

在您的情况下(同时具有超时和可以取消的取消令牌),代码经过最后一个分支,并忽略超时。我认为这要么是错误,要么是文档中应提及的东西。

In your case (with both timeout and a cancellation token that can be cancelled), the code goes through the last branch and ignores the timeout. I think this is either a bug or it is something that should be mentioned in the documentation.

作为解决方法,您可以创建一个单独的 CancellationTokenSource 指定超时并将其链接到主取消源,以便调用方提供(使用 CreateLinkedTokenSource )。当您收到 OperationCancelledException 时,您可以检测到该源是实际取消还是超时:

As a workaround, you can create a separate CancellationTokenSource to specify the timeout and link it to the main cancellation source so that the caller provides (using CreateLinkedTokenSource). When you get OperationCancelledException, you can then detect whether the source was an actual cancellation or a timeout:

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

这篇关于F#Async.Run与超时和cancelToken同步的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆