非同步工作流程 (F#)
本主題描述 F# 中非同步執行計算的支援,即不封鎖其他工作的執行。 例如,非同步計算可以用來撰寫應用程式,而應用程式的 UI 會在應用程式執行其他工作時繼續回應使用者。
async { expression }
備註
在之前的語法中,會設定 expression 代表的計算以非同步執行,也就是說,執行非同步的休眠操作、I/O 及其他非同步操作時,不封鎖目前的計算執行緒。 非同步計算往往會從背景執行緒開始,同時在目前的執行緒繼續執行。 運算式的型別是 Async<'a>,其中 'a 是運算式在使用 return 關鍵字時傳回的型別。 這類運算式中的程式碼稱為「非同步區塊」(Asynchronous Block) 或「非同步區塊」(Async Block)。
有各種方法可以非同步進行程式設計,而 Async 類別提供可支援數種情節的方法。 一般方式是建立代表計算或想要非同步執行之計算的 Async 物件,然後使用其中一個觸發函式來啟動這些計算。 各種觸發函式會提供不同的方法來執行非同步計算,而您想要使用的方法是取決於要使用目前執行緒、背景執行緒還是 .NET Framework 工作物件,以及在計算完成時是否具有應該執行的接續函式。 例如,若要在目前執行緒上啟動非同步計算,您可以使用 Async.StartImmediate。 當您從 UI 執行緒啟動非同步計算時,不會封鎖處理使用者動作 (例如按鍵和滑鼠活動) 的主要事件迴圈,讓您的應用程式可以保持回應。
使用 let! 的非同步繫結
在非同步工作流程中,有些運算式和作業是同步的,有些是較長時間的計算,設計為以非同步方式傳回結果。 以非同步方式呼叫方法時,您使用 let! 繫結,而不是一般的 let 繫結。 let! 的效用是在執行計算的同時,在其他計算或執行緒上繼續執行。 在 let! 繫結右邊傳回時,非同步工作流程的其餘部分會繼續執行。
下列程式碼顯示 let 與 let! 的差異。 使用 let 的程式碼只會建立非同步計算,做為您稍後可以使用 Async.StartImmediate 或 Async.RunSynchronously 執行的物件。 使用 let! 的程式碼會啟動計算,然後在結果可用 (屆時即可繼續執行) 之前暫止執行緒。
// let just stores the result as an asynchronous operation.
let (result1 : Async<byte[]>) = stream.AsyncRead(bufferSize)
// let! completes the asynchronous operation and returns the data.
let! (result2 : byte[]) = stream.AsyncRead(bufferSize)
除了let!外,您可以使用use!執行非同步繫結。 let! 和 use! 的差異與let 和 use 的差異相同。 對於 use!,會在目前的範圍關閉時處置物件。 請注意,目前的 F# 語言版本中,use!不允許將值初始化為 null,即使use 允許。
非同步原始物件
執行單一非同步工作並傳回結果的方法稱為「非同步原始物件」(Asynchronous Primitive),而這些是專為與 let! 搭配使用所設計。 F# 核心程式庫中定義數個非同步原始物件。 Web 應用程式的兩個這類方法是定義於 Microsoft.FSharp.Control.WebExtensions 模組中:WebRequest.AsyncGetResponse 和 WebClient.AsyncDownloadString。 在指定 URL 的情況下,兩個原始物件都會從網頁下載資料。 AsyncGetResponse 會產生 WebResponse 物件,而 AsyncDownloadString 會產生代表網頁之 HTML 的字串。
非同步 I/O 作業的數個原始物件是包含在 Microsoft.FSharp.Control.CommonExtensions 模組中。 Stream 類別的這些擴充方法是 Stream.AsyncRead 和 Stream.AsyncWrite。
F# PowerPack 具有其他非同步原始物件。 您也可以定義其完整主體包括在非同步區塊內的函式,來撰寫自己的非同步原始物件。
若要搭配使用 .NET Framework 中針對其他非同步模型設計的非同步方法與 F# 非同步程式設計模型,您可以建立函式,以傳回 F# Async 物件。 F# 程式庫的函式就可以輕鬆地做到。
這裡包含一個使用非同步工作流的範例:非同步類別文件中還有許多其他範例。
範例
這個範例顯示如何使用非同步工作流程來平行執行計算。
在下面程式碼範例中,fetchAsync 函式會取得從 Web 要求傳回的 HTML 文字。 fetchAsync 函式包含非同步程式碼區塊。 繫結非同步原始物件的結果時,如果是 AsyncDownloadString,則會使用let!,而非 let。
您可以使用 Async.RunSynchronously 函式來執行非同步作業,並等候其結果。 例如,您可以搭配使用 Async.Parallel 函式與 Async.RunSynchronously 函式,平行執行多個非同步作業。 Async.Parallel 函式會採用 Async 物件的清單、設定讓每個 Async 工作物件平行執行的程式碼,以及傳回代表平行計算的 Async 物件。 如同單一作業,您可以呼叫 Async.RunSynchronously 來啟動執行。
runAll 函式會平行啟動三個非同步工作流程,並等到它們全部完成。
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let urlList = [ "Microsoft.com", "https://www.microsoft.com/"
"MSDN", "https://msdn.microsoft.com/"
"Bing", "https://www.bing.com"
]
let fetchAsync(name, url:string) =
async {
try
let uri = new System.Uri(url)
let webClient = new WebClient()
let! html = webClient.AsyncDownloadString(uri)
printfn "Read %d characters for %s" html.Length name
with
| ex -> printfn "%s" (ex.Message);
}
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
runAll()