[英]Why does my second snippet of F# async code work, but the first does not?
注意:我不是一個專業的軟件開發人員,但是我寫了很多不使用異步的代碼,所以如果這個問題真的很直接,我很抱歉。
我正在與用C#編寫的庫連接。 有一個特殊的函數(讓我們稱之為'func')返回'Threading.Tasks.Task>'
我正在使用func在F#中構建一個庫。 我在控制台應用程序中測試了以下代碼,它工作正常。
let result =
func()
|> Async.AwaitTask
|> Async.RunSynchronously
|> Array.ofSeq
但是,當我從WinForms應用程序運行它(這最終是我想要做的)時,表單代碼中的執行塊永遠不會返回。
所以我搞亂了代碼,並嘗試了以下,這是有效的。
let result =
async{
let! temp =
func()
|> Async.AwaitTask
return temp
} |> Async.RunSynchronously |> Array.ofSeq
為什么第一個片段不起作用? 為什么第二個片段有效? 這個頁面上有什么內容可以回答這些問題嗎? 如果是這樣,那似乎並不明顯。 如果沒有,你能指點我到哪兒嗎?
第一個和第二個片段之間的區別在於AwaitTask
調用發生在不同的線程上 。
試試這個驗證:
let printThread() = printfn "%d" System.Threading.Thread.CurrentThread.ManagedThreadId
let result =
printThread()
func()
|> Async.AwaitTask
|> Async.RunSynchronously
|> Array.ofSeq
let res2 =
printThread()
async {
printThread()
let! temp = func() |> Async.AwaitTask
return temp
} |> Async.RunSynchronously |> Array.ofSeq
運行res2
,您將獲得兩行輸出,其中包含兩個不同的數字。 async
運行內部的線程與res2
本身運行的線程不同。 潛入async
會使您處於不同的線程。
現在,這與.NET TPL任務的實際工作方式相互作用。 當你去等待任務時,你不只是在一些隨機線程上得到一個回調,哦不! 相反,您的回調將通過“當前” SynchronizationContext
進行調度。 這是一種特殊的野獸,其中總有一個“當前”的野獸(可以通過靜態屬性訪問 - 談論全球狀態!),你可以要求它安排“在同一個環境中”的東西,其中的概念是“相同的上下文”由實現定義。
當然,WinForms有自己的實現,恰當地命名為WindowsFormsSynchronizationContext
。 當你在WinForms事件處理程序中運行,並且要求當前上下文安排某些事情時,它將使用WinForms自己的事件循環 - 一個Control.Invoke
進行調度。
但是,當然,由於您使用Async.RunSynchronously
阻塞事件循環的線程,因此任務等待永遠不會發生。 你正在等待它發生,它正在等你釋放線程。 阿卡“僵局”。
要解決此問題,您需要在另一個線程上啟動等待,以便不使用WinForms的同步上下文 - 您不小心偶然發現的解決方案。
另一種推薦的解決方案是通過Task.ConfigureAwait
明確告訴TPL不要使用“當前”上下文:
let result =
func().ConfigureAwait( continueOnCapturedContext = false )
|> Async.AwaitTask
|> Async.RunSynchronously
|> Array.ofSeq
不幸的是,這將無法編譯,因為Async.AwaitTask
需要一個Task
,而ConfigureAwait
返回一個ConfiguredTaskAwaitable
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.