F#Async.Run与超时和cancelToken同步 [英] F# Async.RunSynchronously with timeout and cancellationToken
问题描述
在使用超时和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屋!