正确等待F#异步C#方法,返回类型为Task <t>

时间:2017-01-19 13:59:08

标签: c# asynchronous f# async-await

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

所以,我有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<object>,而不是我预期的T中的Task<T>

调用用C#编写的async方法的F#中返回类型为Task<T>并访问T的值的正确方法是什么?

2 个答案:

答案 0 :(得分:15)

首先,在您的用例中,不需要async { }块。 Async.AwaitTask returns an Async<'T>,因此您的async { }块只是展开您获得的Async对象并立即重新包装它。

现在我们已经摆脱了不必要的async阻止,让我们看看你已经获得的类型,以及你想要获得的类型。你有一个Async<'a>,你想要一个'a类型的对象。浏览available Async functions,具有Async<'a> -> 'a类型签名的是Async.RunSynchronously。它需要两个可选参数,一个int和一个CancellationToken,但是如果你把它们排除在外,你就得到了你正在寻找的功能签名。当然,一旦你查看文档,结果证明Async.RunSynchronously F#等价于C#的await,这就是你想要的。那种(但不完全是) )就像C#的await一样。 C#的await是一个可以在async函数中使用的语句,而F#的Async.RunSynchronously使用async对象阻塞当前线程,直到该async对象完成运行。在这种情况下,这正是您正在寻找的。

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

这应该可以帮到你找到你想要的东西。并注意找出所需函数的函数签名的技术,然后查找具有该签名的函数。它将来会有很多帮助。

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

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

  1. 调用一些异步操作
  2. 等待结果
  3. 用这个结果做点什么
  4. 编写该模式的最佳方法如下:

    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!,所以最好写成:{/ p>

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

    所有这些函数都会生成Async<'T>,其中'T将是async块中最终函数的返回类型。要实际运行这些异步块,您可以执行前面提到的Async.RunSynchronously,也可以使用各种Async.Start函数之一(Start,{ {1}},StartImmediateStartAsTask等等。 Async.StartImmediate example也会对Async.SwitchToContext function进行一些讨论,这可能是您想要阅读的内容。但我对StartWithContinuations s不够熟悉,不仅仅是告诉你。

答案 1 :(得分:1)

在这种情况下使用library(data.table) library(ggplot2) library(scales) library(dplyr) pct <- function(x) {x/lag(x)-1} Dates = seq(from = as.Date("2000-01-01"), to =as.Date("2018-10-01"), by = "1 month") set.seed(1024) this_raw = data.frame(CM = Dates, value = rnorm(n = length(Dates)), variable = rep("FAKE",length(Dates))) this_diff = na.omit(as.data.table(this_raw %>% group_by(variable) %>% mutate_each(funs(pct), c(value)))) this_diff$type = "PerCng" this_raw$type = "RAW" plot_all = rbindlist(list(this_raw,this_diff)) plot_all$type = factor(plot_all$type, levels = c("RAW", "PerCng")) out_gg = plot_all %>% ggplot(aes(x=CM, y=value)) + geom_line(color = "royalblue3") + theme(legend.position='bottom')+ ggtitle("FAKE DATA") + facet_wrap(~ type, scale = "free_y", nrow = 2, strip.position = "left", labeller = as_labeller(c(RAW = "Original", PerCng = "% Change") ) )+ scale_x_date(date_breaks = "12 month", date_labels = "%Y-%m", date_minor_breaks = "3 month")+ ylab("")+ theme(plot.title = element_text(hjust = 0.5,size = 12), axis.text.x = element_text(size = 6,angle = 45, hjust = 1), axis.text.y = element_text(size = 6), axis.title.y = element_text(size = 6)) + theme(strip.background = element_blank(), strip.placement = "outside")+ theme(legend.title=element_blank()) print(out_gg) 计算表达式(F#调用基于C#Task的XxxAsync方法)的另一种方法是使用来自以下方面的async计算表达式:

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

Giraffe F#Web框架出于相同的原因而使用task

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