具有超时的F#异步工作流程

Rus*_*tam 8 .net f# asynchronous timeout

我真的很喜欢F#的async工作流程,但就我而言,它有一个严重的问题:它不允许创建应该执行不超过某个特定时间跨度的工作流程.

为了更清楚,这是我为自己写的一个简单的函数:

let withTimeout operation timeout = async {
    try
        return Some <| Async.RunSynchronously (operation, timeout)
    with :? TimeoutException -> return None
}
Run Code Online (Sandbox Code Playgroud)

即签名是

val withTimeout : operation:Async<'a> -> timeout:int -> Async<'a option>
Run Code Online (Sandbox Code Playgroud)

示例用法:

let op = async { 
    do! Async.Sleep(1000) 
    return 1
}
#time
withTimeout op 2000 |> Async.RunSynchronously;;
// Real: 00:00:01.116, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
// val it : unit option = Some 1
withTimeout op 2000 |> Async.RunSynchronously;;
// Real: 00:00:01.004, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
// val it : unit option = Some 1
withTimeout op 500 |> Async.RunSynchronously;;
// Real: 00:00:00.569, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
// val it : unit option = None    
Run Code Online (Sandbox Code Playgroud)

你可以看到,它按预期工作.它非常好,但它也有点尴尬,我不确定它的安全性和可能出现的其他问题.也许我正在重新发明轮子,并且有简洁明了的方式来编写这样的工作流程?

Rus*_*tam 8

UPD:目前最好的选择是提出的Vesa AJK这里:/sf/answers/1836117181/.我的编辑就像这样:

type Async with
    static member WithTimeout (timeout : int option) operation = 
        match timeout with
        | Some time  when time > 0 -> 
            async { 
                let! child = Async.StartChild (operation, time) 
                try 
                    let! result = child 
                    return Some result
                with :? TimeoutException -> return None 
            }
        | _ -> 
            async { 
                let! result = operation
                return Some result
            }
Run Code Online (Sandbox Code Playgroud)

这是另一种选择:

type Async with
    static member WithCancellation (token:CancellationToken) operation = 
        async {
            try
                let task = Async.StartAsTask (operation, cancellationToken = token)
                task.Wait ()
                return Some task.Result
            with 
                | :? TaskCanceledException -> return None
                | :? AggregateException -> return None
        }

    static member WithTimeout (timeout:int option) operation = 
        match timeout with
        | Some(time) -> 
            async {
                use tokenSource = new CancellationTokenSource (time)
                return! operation |> Async.WithCancellation tokenSource.Token
            }

        | _ -> 
            async { 
                let! res = operation
                return Some res
            }
Run Code Online (Sandbox Code Playgroud)

在这里,我使用.Net任务和CancellationToken.


Mis*_*hor 7

请参阅 的这个实现Async.WhenAny,它的行为应该类似于Task.WhenAny

通过使用它,您可以实现withTimeout类似于Task 此处的实现方式:

let withTimeout dueTime comp =
    let success = async {
        let! x = comp
        return (Some x)
    }
    let timeout = async {
        do! Async.Delay(dueTime)
        return None
    }
    Async.WhenAny(success, timeout)
Run Code Online (Sandbox Code Playgroud)

我不确定它会在第一个计算完成后取消另一个计算,但至少这个实现不会不必要地阻塞线程。


Sir*_*eos 7

只需使用Async.StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>

let with_timeout timeout action =
  async {
    let! child = Async.StartChild( action, timeout )
    return! child
  }
Run Code Online (Sandbox Code Playgroud)