module Memoize //More generic variant of http://www.fssnip.net/c4 let memoize (f: 'a -> 'b) = let cache = System.Collections.Concurrent.ConcurrentDictionary<'a, 'b>() fun x -> cache.GetOrAdd(x, f) // and this works also with F# async functions. let memoizeAsync f = let cache = System.Collections.Concurrent.ConcurrentDictionary<'a, System.Threading.Tasks.Task<'b>>() fun x -> // task.Result serialization to sync after done. cache.GetOrAdd(x, fun x -> f(x) |> Async.StartAsTask) |> Async.AwaitTask (* [] let ``async cache should work``() = let mutable x = 0 let someSlowFunc mykey = async { Console.WriteLine "Simulated downloading..." do! Async.Sleep 400 Console.WriteLine "Simulated downloading Done." x <- x + 1 // Side effect! return "" } let memFunc = memoizeAsync <| someSlowFunc async { do! memFunc "a" |> Async.Ignore do! memFunc "a" |> Async.Ignore do! memFunc "a" |> Async.Ignore do! [|1 .. 30|] |> Seq.map(fun _ -> (memFunc "a")) |> Async.Parallel |> Async.Ignore for i = 1 to 30 do Async.Start( memFunc "a" |> Async.Ignore ) Async.Start( memFunc "a" |> Async.Ignore ) do! Async.Sleep 500 do! memFunc "a" |> Async.Ignore do! memFunc "a" |> Async.Ignore for i = 1 to 30 do Async.Start( memFunc "a" |> Async.Ignore ) do! [|1 .. 30|] |> Seq.map(fun _ -> (memFunc "a")) |> Async.Parallel |> Async.Ignore } |> Async.RunSynchronously x |> shouldEqual 1 *)