I’d like to implement a retry utility for TPL-Tasks in F#, for retrying things like HttpClient.GetAsync(...).
Specification: it will retry a given task-starting function up to n times by swallowing any exceptions that might be thrown. But for the last iteration, it should propagate any exceptions to the caller rather than swallowing it (with a meaningful stack trace).
In C#, it’d be as simple as
public static async Task<T> RetryTask<T>(int n, D<T> d) {
for (int i = 0; i < n - 1; ++i) {
try {
return await d.Invoke();
} catch (Exception) { }
}
return await d.Invoke();
}
https://sharplab.io/#gist:e6b5416c410e879e8ddbd067cdd9a9c2
But my best attempt in F# looks as cluttered as
let retryTask n (f: unit -> 'T Task): 'T Task = task {
match!
task {
let mutable found = false
let mutable value = Unchecked.defaultof<'T>
let mutable i = 0
while i < n - 1 && not found do
if 0 < i then
do! Task.Delay(TimeSpan.FromMilliseconds(100. * float i))
i <- i + 1
try
let! ret = f()
found <- true
value <- ret
with _ -> ()
return if found then Some value else None
}
with | Some ret -> return ret
| None -> return! f()
}
https://sharplab.io/#gist:65c3fdadacc470127b9cfa8bb2e4cb0e
THREE mutable variables! Am I missing anything?
>Solution :
Here’s my solution:
let retry nTimes makeTask =
let rec loop nTimes =
task {
try return! makeTask ()
with _ when nTimes > 0 ->
do! Async.Sleep 1000
return! loop (nTimes - 1)
}
loop nTimes