[英]Correctly awaiting in F# an async C# method with return type of Task<T>
我希望能夠從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
的值的正確方法是什么?
首先,在您的用例中,不需要async { }
塊。 Async.AwaitTask
返回一個Async<'T>
,因此你的async { }
塊只是打開你得到的Async
對象並立即重新包裝它。
現在我們已經擺脫了不必要的async
塊,讓我們看看你得到的類型,以及你想要獲得的類型。 你有一個Async<'a>
,你想要一個'a
類型的對象。 查看可用的Async
函數 ,具有Async<'a> -> 'a
等類型簽名的函數是Async.RunSynchronously
。 它需要兩個可選參數,一個是int
和一個CancellationToken
,但是如果你把它們留下來,你就得到了你正在尋找的函數簽名。 當然,一旦你看到文檔,它就會發現Async.RunSynchronously
相當於F#的F# 有點像(但不完全一樣)像C#的await
,這就是你想要的。 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:如果要在不阻塞當前線程的情況下等待異步結果,它通常是模式的一部分,如下所示:
編寫該模式的最佳方法如下:
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!
除外let!
其次是return
完全相當於return!
,所以寫得更好:
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
, StartImmediate
, StartAsTask
, StartWithContinuations
等)。 Async.StartImmediate
示例也討論了Async.SwitchToContext
函數 ,這可能是您想要閱讀的內容。 但是我對SynchronizationContext
不太熟悉,不僅僅是告訴你。
在這種情況下使用async
計算表達式(F#調用C#基於任務的XxxAsync方法)的另一種方法是使用以下task
計算表達式:
https://github.com/rspeele/TaskBuilder.fs
Giraffe F #Web框架使用task
的原因大致相同:
https://github.com/giraffe-fsharp/Giraffe/blob/develop/DOCUMENTATION.md#tasks
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.