【问题标题】:Correctly awaiting in F# an async C# method with return type of Task<T>在 F# 中正确等待返回类型为 Task<T> 的异步 C# 方法
【发布时间】:2017-06-04 06:12:58
【问题描述】:

我希望能够使用 F# 中的 C# 库。大多数情况下,这非常简单。但是,如果我尝试调用返回 Task&lt;T&gt; 的函数,我将无法获得返回值。

所以,我有以下定义的 C# 方法:

public async Task<TEvent> ReadEventAsync<TEvent>(string streamName, int position) where TEvent: class

我正在尝试从 F# 中使用此方法,如下所示:

let readEventFromEventStore<'a when 'a : not struct> (eventStore:IEventStoreRepository) (streamName:string) (position:int) = 
     async {
            return eventStore.ReadEventAsync(streamName, position) 
            |> Async.AwaitTask
            }

我将这个函数部分应用于IEventStoreRepository 的实例和我希望从中检索事件的流名称:

let readEvent = readEventFromEventStore eventStore streamName

然后,最后,我应用剩余的参数:

let event = readEvent StreamPosition.Start

当我得到event 的值时,它是FSharpAsync&lt;object&gt;,而不是我预期的Task&lt;T&gt; 中的T

在 F# 中调用用 C# 编写的 async 方法,返回类型为 Task&lt;T&gt; 并访问 T 的值的正确方法是什么?

【问题讨论】:

  • 试试return! eventStore. ...而不是return eventStore. ...
  • @ildjarn - 刚刚在那里尝试过。不幸的是,它返回完全相同的结果,即FSharpAsync&lt;object&gt;

标签: c# asynchronous f# async-await


【解决方案1】:

在这种情况下(F# 调用 C# 基于任务的 XxxAsync 方法)使用 async 计算表达式的替代方法是使用来自以下位置的 task 计算表达式:

https://github.com/rspeele/TaskBuilder.fs

Giraffe F# Web 框架使用 task 的原因大致相同:

https://github.com/giraffe-fsharp/Giraffe/blob/develop/DOCUMENTATION.md#tasks

【讨论】:

    【解决方案2】:

    首先,在您的用例中,不需要async { } 块。 Async.AwaitTask returns an Async&lt;'T&gt;,所以你的 async { } 块只是解开你得到的 Async 对象并立即重新包装它。

    现在我们已经摆脱了不必要的async 块,让我们看看您获得的类型,以及您想要获得的类型。你有一个Async&lt;'a&gt;,并且你想要一个'a 类型的对象。查看available Async functions,具有Async&lt;'a&gt; -&gt; 'a 之类的类型签名的是Async.RunSynchronously。它需要两个可选参数,一个int 和一个CancellationToken,但如果你把它们去掉,你就会得到你正在寻找的函数签名。果然,一旦您查看文档,就会发现 Async.RunSynchronouslyC# 的 await 的 F# 等价物,这正是您想要的。 有点(但不完全是)类似于 C# 的 @ 987654341@。 C# 的 await 是可以在 async 函数中使用的语句,而 F# 的 Async.RunSynchronously 采用 async 对象阻塞当前线程,直到 async 对象完成运行。在这种情况下,这正是您要寻找的。​​p>

    let readEventFromEventStore<'a when 'a : not struct> (eventStore:IEventStoreRepository) (streamName:string) (position:int) =
        eventStore.ReadEventAsync(streamName, position) 
        |> Async.AwaitTask
        |> Async.RunSynchronously
    

    这应该可以满足您的需求。并注意找出所需函数的函数签名的技术,然后寻找具有该签名的函数。对未来会有很大帮助。

    更新:感谢 Tarmil 指出我在 cmets 中的错误:Async.RunSynchronously 等同于 C# 的 await。它非常相似,但是由于RunSynchronously 阻塞了当前线程,因此需要注意一些重要的细节。 (你不想在你的 GUI 线程中调用它。)

    更新 2:当您想在不阻塞当前线程的情况下等待异步结果时,通常是这样的模式的一部分:

    1. 调用一些异步操作
    2. 等待结果
    3. 用这个结果做点什么

    编写该模式的最佳方式如下:

    let equivalentOfAwait () =
        async {
            let! result = someAsyncOperation()
            doSomethingWith result
        }
    

    以上假设doSomethingWith 返回unit,因为您调用它是因为它的副作用。如果它返回一个值,你会这样做:

    let equivalentOfAwait () =
        async {
            let! result = someAsyncOperation()
            let value = someCalculationWith result
            return value
        }
    

    或者,当然:

    let equivalentOfAwait () =
        async {
            let! result = someAsyncOperation()
            return (someCalculationWith result)
        }
    

    假设someCalculationWith 不是异步操作。相反,如果您需要将两个异步操作链接在一起,其中第二个使用第一个的结果 - 或者甚至以某种序列的三个或四个异步操作 - 那么它看起来像这样:

    let equivalentOfAwait () =
        async {
            let! result1 = someAsyncOperation()
            let! result2 = nextOperationWith result1
            let! result3 = penultimateOperationWith result2
            let! finalResult = finalOperationWith result3
            return finalResult
        }
    

    除了let! 后跟return 完全等价于return!,所以最好写成:

    let equivalentOfAwait () =
        async {
            let! result1 = someAsyncOperation()
            let! result2 = nextOperationWith result1
            let! result3 = penultimateOperationWith result2
            return! (finalOperationWith result3)
        }
    

    所有这些函数都将产生一个Async&lt;'T&gt;,其中'T 将是async 块中最终函数的返回类型。要真正运行这些异步块,您可以像已经提到的那样执行Async.RunSynchronously,或者您可以使用各种Async.Start 函数之一(StartStartImmediateStartAsTaskStartWithContinuations 等)。 Async.StartImmediate example 也谈到了Async.SwitchToContext function,这可能是您想要阅读的内容。但我对SynchronizationContexts 不够熟悉,无法告诉您更多信息。

    【讨论】:

    • 成功了。谢谢。也感谢指针重新查看函数签名。
    • 呃,这里有一些错误信息。也就是说,Async.RunSynchronously 绝对不是 C# 的 await 的 F# 等价物。它更像C#的task.Wait(),即阻塞当前线程直到任务完成。 async {} 块中的 let! 相当于 F# 中的 await
    • @Tarmil - 感谢您的关注!修正了我的错误信息;希望编辑后答案现在完全正确。
    • 感谢您的信息! F# 中是否有任何方法可以等待(原文如此)异步 C# 方法的结果而不阻塞当前线程(并且不需要在恢复以下代码时恢复原始 SynchronizationContext)?我想我的意思是说:是否有一个精确的 F# 等效 await SomeAsyncMethod().ConfigureAwait(false)
    • 我对@9​​87654382@ 不够熟悉,无法确定其中的微妙之处,但在F# 中,一般模式是在async 块内,您可以使用let! result = someAsyncOperation()。注意感叹号。这将等待someAsyncOperation,然后将其值绑定到result。我会用更多细节更新我的答案,因为评论不是编写代码示例的最佳位置。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-14
    • 1970-01-01
    • 2011-10-24
    • 2020-04-12
    • 1970-01-01
    • 2018-10-13
    • 1970-01-01
    相关资源
    最近更新 更多