为什么我必须将Async <t>包装到另一个异步工作流中并让它!它?

时间:2017-11-16 12:56:53

标签: asynchronous f# let async-workflow

我试图了解F#中的异步工作流程,但我找到了一个我真正理解并希望有人可以帮助我的部分。

以下代码可以正常使用:

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)

我定义了一个异步工作流,其中TryOpenAsync返回一个Task类型。我用Async.AwaitTask将它转换为Async。 (副任务:&#34;等待&#34;任务?它没有等待它转换它,是吗?我认为它与Task.Wait或await关键字无关)。我等待#34;它让我们!并返回它。 要启动工作流,我使用RunSynchronously,它应该启动工作流并返回结果(绑定它)。在结果上,我检查是否找不到流。

但现在是我的第一个问题。为什么我必须在另一个异步计算中包装TryOpenAsync调用并让它! (&#34;等待&#34;)它? 例如。以下代码不起作用:

let asynWorkflow =  Stream.TryOpenAsync(partition) |> Async.AwaitTask  

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

我认为AwaitTask使它成为异步并且RunSynchronously应该启动它。然后使用结果。我错过了什么?

我的第二个问题是为什么有#34; Async.Let!&#34;功能可用?也许是因为它不起作用或更好,为什么它不适用于以下代码?

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)

我只是将TryOpenAsync作为参数插入,但它不起作用。通过说不起作用我的意思是整个FSI将挂起。所以它与我的async /&#34; await&#34;。

有关

---更新:

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

FSI中无法正常工作的结果:

>

你不能再在FSI中执行任何东西了

---更新2

我使用的是Streamstone。这里是C#示例:https://github.com/yevhen/Streamstone/blob/master/Source/Example/Scenarios/S04_Write_to_stream.cs

这里是Stream.TryOpenAsync:https://github.com/yevhen/Streamstone/blob/master/Source/Streamstone/Stream.Api.cs#L192

3 个答案:

答案 0 :(得分:4)

我不能告诉你为什么第二个例子不知道Streampartition是什么以及它们是如何工作的。

但是,我想借此机会指出,这两个例子并非严格等同于

F#async有点像做什么的“食谱”。当您编写async { ... }时,结果计算只是坐在那里,而不是实际做任何事情。它更像是声明一个函数而不是发出一个命令。只有当您通过调用类似Async.RunSynchronouslyAsync.Start的内容“启动”它时,它才真正运行。一个必然结果是,您可以多次启动相同的异步工作流 ,并且每次都会成为一个新的工作流程。与IEnumerable的工作方式非常相似。

另一方面,C#Task更像是对已经运行的异步计算的“引用”。一旦调用Stream.TryOpenAsync(partition),计算就会开始,并且在任务实际开始之前无法获得Task实例。您可以多次await生成Task,但每个await都不会导致重新尝试打开流。只有第一个await实际上会等待任务完成,而后续的每个{J}只会返回相同的记忆结果。

在异步/反应术语中,F#async就是你所说的“冷”,而C#Task则称为“热”。

答案 1 :(得分:3)

第二个代码块看起来应该对我有用。如果我为StreamStreamOpenResult提供虚拟实现,它会运行它。

您应该尽可能避免使用Async.RunSynchronously因为它违背了异步的目的。将所有此代码放在较大的async块中,然后您就可以访问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
    }

您可能需要在程序的最外边缘放置Async.StartAsync.RunSynchronously才能实际运行它,但如果您拥有async(或将其转换为a Task)并将其传递给其他可以非阻塞方式调用它的代码(例如Web框架)。

答案 2 :(得分:3)

并不是说我想用另一个问题回答你的问题,但是:你为什么要做这样的代码呢?这可能有助于理解它。为什么不呢:

COUNT(DISTINCT(CASE WHEN ...

创建异步工作流程只是为了立即调用let asyncWorkflow = async { let! result = Stream.TryOpenAsync(partition) |> Async.AwaitTask if result.Found then return openResult.Stream else return Stream(partition) } - 这与在RunSynchronously上调用.Result类似 - 它只是阻止当前线程,直到工作流返回。