简体   繁体   中英

F# async workflow with timeout

I really like F#'s async workflow, but as for me, it has one serious issue: it does not allow creating workflows which should execute no longer than some specific timespan.

To make it clearer, here's a simple function I wrote for myself:

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

Ie signature is

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

Example usage here:

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    

You can see, it works as expected. And it's very nice, but it is also a bit awkward and I'm not sure of it's safety and other issues which might arise. Maybe I am reinventing the wheel, and there's nice and concise way to write such workflows?

UPD: Best option at the moment was proposed by Vesa AJK here: https://stackoverflow.com/a/26230245/1554463 . With my edits its like that:

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
            }

Here's another option:

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
            }

Here I use .Net tasks and CancellationToken .

Please see this implementation of Async.WhenAny , which is supposed to behave similar to Task.WhenAny .

By using it, you can implement withTimeout similarly to how it is implemented for Task here :

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)

I'm not confident that it will cancel the other computation when the first one finishes, but at least this implementation won't block a thread unnecessarily.

Just use Async.StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>> :

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM