2017-01-19 26 views
6

的返回類型我想能夠消耗來自F#C#的庫。大多數情況下這是非常簡單的。但是,如果我嘗試調用返回Task<T>的函數,我無法獲取返回的值。在F#正確等待異步C#方法使用任務<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>

什麼是調用一個async方法寫在C#與Task<T>返回類型和訪問的T值的F#的正確方法是什麼?

+1

嘗試'返回! eventStore。 ...而不是'return eventStore。 ...'? – ildjarn

+0

@ildjarn - 剛剛在那裏試過。不幸的是,它返回完全相同的結果,即'FSharpAsync '。 –

回答

9

首先,在您的使用情況下,沒有必要爲async { }塊。 Async.AwaitTask returns an Async<'T>,所以你的async { }塊只是解開你得到的Async對象,並立即重新包裝它。

現在我們已經擺脫了不必要的async塊,讓我們看看你得到的類型,以及你想要想要的類型。你得到了一個Async<'a>,並且你想要一個'a類型的對象。查看available Async functions,類型簽名如Async<'a> -> 'a的那個是Async.RunSynchronously。它有兩個可選參數,一個intCancellationToken,但如果你離開了這些,你有你要找的函數簽名。果然,當你在看文檔事實證明,Async.RunSynchronously的F#等效的await C#,這是你想要的。(但不完全)像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. 我與那結果

東西寫的是p中的最佳途徑attern如下:上述

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<'T>,其中'T將在最後的函數的返回類型async塊。要真正運行的異步塊,你要麼Async.RunSynchronously前面已經提到,或者你可以使用的各種Async.Start功能之一(StartStartImmediateStartAsTaskStartWithContinuations,等等)。 Async.StartImmediate example也談到了一些關於Async.SwitchToContext function,這可能是你想要閱讀的東西。但我不熟悉SynchronizationContext s告訴你更多。

+0

那釘了它。謝謝。還要感謝指針重新查看函數簽名。 –

+3

恩,這裏有一些誤導。也就是說,'Async.RunSynchronously'絕對不是C#的'await'的F#等價物。它更像C#的'task.Wait()',即它阻塞當前線程直到任務完成。 'async {}'塊中的'let!'是F#的'await'。 – Tarmil

+0

@Tarmil - 感謝您的支持!修正了我的錯誤信息;希望編輯後的答案現在完全正確。 – rmunn