簡體   English   中英

正確等待F#異步C#方法,返回類型為Task <T>

[英]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>而不是TTask<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# 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. 做一些結果

編寫該模式的最佳方法如下:

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函數之一( StartStartImmediateStartAsTaskStartWithContinuations等)。 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM