首页 文章

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

提问于
浏览
5

我正在尝试理解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 回答

  • 4

    我可以't tell you why the second example doesn' t工作,不知道 Streampartition 是什么以及它们是如何工作的 .

    但是,我想借此机会指出,这两个例子并不完全相同 .

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

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

    在异步/反应式术语中,F# async 是您所谓的"cold",而C# Task 则称为"hot" .

  • 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 (或将其转换为 Task )并将其传递给其他代码(例如Web框架)则会更好)可以以非阻塞方式调用它 .

  • 3

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

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

    创建异步工作流只是为了立即调用 RunSynchronously 没什么意义 - 它类似于在 Task 上调用 .Result - 它只是阻止当前线程直到工作流返回 .

相关问题