Let's say I want to call, from F#, this C# function:
public static class Foo {
public Bar Baz()
{
...
}
}
The problem is this function is CPU intensive and I don't want to block on it. Unfortunately the C# library doesn't have an Task<Bar> BazAsync()
overload.
Then I want to provide the asynchronous version myself, by creating an F# function that calls it and returns an (already started) Async<Bar>
. That is, I don't want to use System.Threading.Task<Bar>
.
I guess what I'm looking for is the equivalent of Task<T>.Run()
in the F# Async way of doing things.
I've already looked at the following options:
Async.StartAsTask
-> deals with C#ish Task type.Async.Start
and Async.StartImmediately
-> receive Async<unit>
not Async<T>
Is Async.StartChild
what I'm looking for?
If yes, it would be:
let BazWrapper() =
let asyncTask: Async<Bar> = async {
return Foo.Bar()
}
Async.StartChild asyncTask
However, if this above is the solution:
Async<Async<Bar>>
instead of Async<Bar>
?BazWrapper returns Async<Async<Bar>>
because that's what StartChild does: it takes an Async<'T>
and returns Async<Async<'T>>
. The intent is to use it within an async computation expression, so that you can launch multiple "child" asyncs. Example from the Async.Start vs Async.StartChild sample code:
async {
//(...async stuff...)
for msg in msgs do
let! child = asyncSendMsg msg |> Async.StartChild
()
//(...more async stuff...)
}
When you're inside an async
computation expression, the let!
keyword will "unwrap" an Async<'Whatever>
leaving you with a value of type 'Whatever
. In the case of calling Async.StartChild
, the 'Whatever
type is specifically Async<'T>
.
Therefore, if you want to return an already-started async via Async.StartChild
, the way to do it would be:
let BazWrapper() =
let asyncTask: Async<Bar> = async {
return Foo.Bar()
}
async {
let! child = Async.StartChild asyncTask
return! child
}
However, I suspect that you'll find that an already-started async isn't as useful to you as a "cold" async (one that isn't started yet), because a "cold" async can still be composed with other async tasks before starting it. (This can be useful for, for example, wrapping logging around your asyncs.) So if I were writing the code for your situation, I'd probably simply do this:
let BazWrapper() =
async {
return Foo.Bar()
}
And now BazWrapper() returns an Async that has not been started yet, and you can either start that with Async.RunSynchronously
if you want the value right away, or use it in other async
computation expressions.