Why should I wrap Async <T> in another async workflow and allow it! It?

I am trying to understand asynchronous workflows in F #, but I have found one part that I really don’t understand, and I hope someone can help me with this.

The following code works fine:

let asynWorkflow = async{ let! result = Stream.TryOpenAsync(partition) |> Async.AwaitTask return result } let stream = Async.RunSynchronously asynWorkflow |> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition) 

I define an asynchronous workflow when TryOpenAsync returns the type of the task. I am converting it to Async with Async.AwaitTask. (Side quest: “Await” Task? It doesn't wait for it, it just converts it, does it? I think this has nothing to do with Task.Wait or the await keyword). I am "waiting" for it with let! and return it. To start a workflow, I use RunSynchronously, which should start the workflow and return the result (bind it). As a result, I will check if Stream is found.

But now to my first question. Why should I wrap the TryOpenAsync call in another async calculation and let it! ("wait")? For instance. The following code does not work:

 let asynWorkflow = Stream.TryOpenAsync(partition) |> Async.AwaitTask let stream = Async.RunSynchronously asynWorkflow |> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition) 

I thought that AwaitTask makes it Async and RunSynchronously should start it. Then use the result. What am I missing?

My second question is: why does "Async.Let!" Exist? function available? Maybe because it doesn't work, or better, why doesn't it work with the following code?

 let ``let!`` task = async{ let! result = task |> Async.AwaitTask return result } let stream = Async.RunSynchronously ( ``let!`` (Stream.TryOpenAsync(partition)) ) |> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition) 

I just insert TryOpenAsync as a parameter, but it does not work. Saying that does not work, I mean that the entire FSI will hang. So this has something to do with my asynchronous / "wait".

--- Update:

Result of working code in FSI:

 > Real: 00:00:00.051, CPU: 00:00:00.031, GC gen0: 0, gen1: 0, gen2: 0 val asynWorkflow : Async<StreamOpenResult> val stream : Stream 

The result of non-working code in FSI:

 > 

And you can't do anything else in FSI

--- Update 2

I am using Streamstone. Here's a C # example: https://github.com/yevhen/Streamstone/blob/master/Source/Example/Scenarios/S04_Write_to_stream.cs

and here Stream.TryOpenAsync: https://github.com/yevhen/Streamstone/blob/master/Source/Streamstone/Stream.Api.cs#L192

+5
source share
3 answers

I can’t tell you why the second example does not work, not knowing what Stream and partition and how they work.

However, I want to take this opportunity to indicate that the two examples are not strictly equivalent.

F # async is a kind of “recipe” for what to do. When you write async { ... } , the resulting calculation just sits there, but actually does nothing. This is more like declaring a function than issuing a command. Only when you "start" it, causing something like Async.RunSynchronously or Async.Start , does it really start. The consequence is that you can run the same async workflow several times, and each time it will be a new workflow. Very similar to how IEnumerable works.

C # Task , on the other hand, is more like a “link” to an already done async calculation. The calculation starts as soon as you call Stream.TryOpenAsync(partition) , and it is not possible to get an instance of Task before the task starts. You can await resulting Task several times, but each await will not lead to a new attempt to open the stream. Only the first await really waits for the completion of the task, and each subsequent one will only return you the same remembered result.

In asynchronous / reactive jargon, F # async is what you call "cold" and C # Task is called "hot."

+4
source

The second block of code looks like it works with me. It runs it if I provide dummy implementations for Stream and StreamOpenResult .

You should avoid using Async.RunSynchronously where possible because it defeats the async target. Put all this code in a larger async block, and then you will get access to StreamOpenResult :

 async { let! openResult = Stream.TryOpenAsync(partition) |> Async.AwaitTask let stream = if openResult.Found then openResult.Stream else Stream(partition) return () // now do something with the stream } 

You may need to place Async.Start or Async.RunSynchronously at the very outer edge of your program to run it, but it is better if you have async (or convert it to Task ) and pass it to another code (for example, a web framework) which can cause it in a non-blocking manner.

+3
source

Not that I would like to answer your question with another question, but: why are you doing this code? This might help to understand this. Why not just:

 let asyncWorkflow = async { let! result = Stream.TryOpenAsync(partition) |> Async.AwaitTask if result.Found then return openResult.Stream else return Stream(partition) } 

You do not need to create an async workflow just to immediately call RunSynchronously on it - it looks like calling .Result on Task - it just blocks the current thread until the workflow returns.

+3
source

Source: https://habr.com/ru/post/1273434/


All Articles