我正在尝试理解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关键字无关) . 让我“等待”它!并返回它 . 要启动工作流,我使用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使它成为Async和RunSynchronously应该启动它 . 然后使用结果 . 我错过了什么?
我的第二个问题是为什么有“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将挂起 . 所以它与我的异步/“等待”有关 .
---更新:
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 回答
我可以't tell you why the second example doesn' t工作,不知道
Stream
和partition
是什么以及它们是如何工作的 .但是,我想借此机会指出,这两个例子并不完全相同 .
F#
async
有点像"recipe"做什么 . 当你写async { ... }
时,结果计算只是坐在那里,而不是实际做任何事情 . 它更像是声明一个函数而不是发出一个命令 . 只有当你"start"通过调用类似Async.RunSynchronously
或Async.Start
之类的东西它才真正运行 . 一个必然结果是,您可以多次启动相同的异步工作流,并且每次都将成为一个新的工作流 . 与IEnumerable
的工作方式非常相似 .另一方面,C#
Task
更像是已经运行的异步计算"reference" . 一旦调用Stream.TryOpenAsync(partition)
,计算就会开始,并且在任务实际启动之前无法获得Task
实例 . 您可以await
多次生成Task
,但每个await
都不会导致重新尝试打开流 . 只有第一个await
实际上会等待任务完成,而后续的每一个都只会返回相同的记忆结果 .在异步/反应式术语中,F#
async
是您所谓的"cold",而C#Task
则称为"hot" .第二个代码块看起来应该对我有用 . 如果我为
Stream
和StreamOpenResult
提供虚拟实现,它会运行它 .你应该尽可能避免使用
Async.RunSynchronously
,因为它会破坏异步的目的 . 将所有这些代码放在一个更大的async
块中,然后您就可以访问StreamOpenResult
:您可能需要在程序的最外边缘放置一个
Async.Start
或Async.RunSynchronously
来实际运行它,但如果您拥有async
(或将其转换为Task
)并将其传递给其他代码(例如Web框架)则会更好)可以以非阻塞方式调用它 .并不是说我想用另一个问题回答你的问题,但是:为什么你这样做的代码呢?这可能有助于理解它 . 为什么不呢:
创建异步工作流只是为了立即调用
RunSynchronously
没什么意义 - 它类似于在Task
上调用.Result
- 它只是阻止当前线程直到工作流返回 .