我试图了解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。 (副任务:"等待"任务?它没有等待它转换它,是吗?我认为它与Task.Wait或await关键字无关)。我等待#34;它让我们!并返回它。 要启动工作流,我使用RunSynchronously,它应该启动工作流并返回结果(绑定它)。在结果上,我检查是否找不到流。
但现在是我的第一个问题。为什么我必须在另一个异步计算中包装TryOpenAsync调用并让它! ("等待")它? 例如。以下代码不起作用:
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!"功能可用?也许是因为它不起作用或更好,为什么它不适用于以下代码?
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 /" await"。
有关---更新:
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
答案 0 :(得分:4)
我不能告诉你为什么第二个例子不知道Stream
和partition
是什么以及它们是如何工作的。
但是,我想借此机会指出,这两个例子并非严格等同于。
F#async
有点像做什么的“食谱”。当您编写async { ... }
时,结果计算只是坐在那里,而不是实际做任何事情。它更像是声明一个函数而不是发出一个命令。只有当您通过调用类似Async.RunSynchronously
或Async.Start
的内容“启动”它时,它才真正运行。一个必然结果是,您可以多次启动相同的异步工作流 ,并且每次都会成为一个新的工作流程。与IEnumerable
的工作方式非常相似。
Task
更像是对已经运行的异步计算的“引用”。一旦调用Stream.TryOpenAsync(partition)
,计算就会开始,并且在任务实际开始之前无法获得Task
实例。您可以多次await
生成Task
,但每个await
都不会导致重新尝试打开流。只有第一个await
实际上会等待任务完成,而后续的每个{J}只会返回相同的记忆结果。
在异步/反应术语中,F#async
就是你所说的“冷”,而C#Task
则称为“热”。
答案 1 :(得分:3)
第二个代码块看起来应该对我有用。如果我为Stream
和StreamOpenResult
提供虚拟实现,它会运行它。
您应该尽可能避免使用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.Start
或Async.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
类似 - 它只是阻止当前线程,直到工作流返回。