Dot-Net

帶有超時的 F# 非同步工作流

  • September 23, 2015

我真的很喜歡 F# 的async工作流程,但對我來說,它有一個嚴重的問題:它不允許創建執行時間不超過某個特定時間跨度的工作流程。

為了更清楚,這是我為自己編寫的一個簡單函式:

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

即簽名是

val withTimeout : operation:Async<'a> -> timeout:int -> Async<'a option>

此處的範例用法:

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    

您可以看到,它按預期工作。它非常好,但也有點尷尬,我不確定它的安全性和可能出現的其他問題。也許我正在重新發明輪子,並且有一種簡潔明了的方式來編寫這樣的工作流程?

UPD:Vesa AJK在這裡提出了目前的最佳選擇: https ://stackoverflow.com/a/26230245/1554463 。我的編輯是這樣的:

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
           }

這是另一種選擇:

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
           }

在這裡,我使用 .Net 任務和CancellationToken.

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

let with_timeout timeout action =
 async {
   let! child = Async.StartChild( action, timeout )
   return! child
 }

引用自:https://stackoverflow.com/questions/22245268