I understand that the main difference between f# and c# async model is that in f#, the async execution does not begin unless you call something like Async.RunSynchronously. In c#, when a method returns a task, execution is usually (not always) immediately started in a background thread.
Async.AwaitTask documentation says "Returns an asynchronous computation that waits for the given task to complete and returns its result."
Does that mean, when you call a c# method that returns a task, execution already started in the background? If so, what is the point of wrapping it inside an Async type anymore?
The point of wrapping a Task inside an Async is to more easily compose it with other asyncs, or to use it with let!
from inside an async { ... }
block. And in the latter case, the wrapped Task won't be started until its enclosing async { ... }
block is started.
For example, let's look at the following function:
let printTask str =
async {
printfn "%s" str
} |> Async.StartAsTask
This doesn't do much; its only reason for existence is so that you can tell when it has started running, because it will print a message to the screen. If you call it from F# Interactive:
printTask "Hello"
You'll see the following output:
Hello
val it : Threading.Tasks.Task<unit> =
System.Threading.Tasks.Task`1[Microsoft.FSharp.Core.Unit]
{AsyncState = null;
CreationOptions = None;
Exception = null;
Id = 4;
IsCanceled = false;
IsCompleted = true;
IsCompletedSuccessfully = true;
IsFaulted = false;
Status = RanToCompletion;}
So it printed "Hello" and then returned the completed Task. This proves that the Task was started immediately.
But now look at the following code:
open System.Net
open System
open System.IO
let printTask str =
async {
printfn "%s" str
} |> Async.StartAsTask
let fetchUrlAsync url =
async {
let req = WebRequest.Create(Uri(url))
do! printTask ("started downloading " + url) |> Async.AwaitTask
use! resp = req.GetResponseAsync() |> Async.AwaitTask
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
let html = reader.ReadToEnd()
do! printTask ("finished downloading " + url) |> Async.AwaitTask
}
(This is Scott Wlaschin's "Async Web Downloader" example, adapted to use Tasks instead of Asyncs internally).
Here, the async { ... }
block contains three Tasks, all of which are wrapped in Async.AwaitTask
. (Note that if you removed |> Async.AwaitTask
from any of these lines, you'd get a type error). For each Task, once its line of code executes, it will start right away. But that's an important point, because the overall async { ... }
computation is not started right away. So I can do:
let a = fetchUrlAsync "http://www.google.com"
And the only thing printed in F# Interactive is val a : Async<unit>
. I can wait as long as I want to, and nothing else is printed. Only once I actually start a
will it start running:
a |> Async.RunSynchronously
This prints started downloading http://www.google.com
immediately, then after a short pause it prints finished downloading http://www.google.com
.
So that's the purpose of Async.AwaitTask
: to allow async { ... }
blocks to more easily interoperate with C# code that returns Tasks. And if the Async.AwaitTask call is inside an async { ... }
block, then the Task won't actually start until the enclosing Async
is started, so you still get all the advantages of "cold-start" Asyncs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With