非同步運算式
本文說明 F# 對非同步運算式的支援。 非同步運算式提供了執行非同步計算的一種方式,也就是在不封鎖其他工作執行的情況下執行計算。 例如,非同步計算可用來撰寫應用程式。這些應用程式在執行其他工作時,其使用者介面可繼續回應使用者。 F# 非同步工作流程程式設計模型可讓您撰寫函式型程式,同時隱藏程式庫中執行緒轉換的詳細資料。
您也可以使用可以直接建立 .NET 工作的工作運算式,來撰寫非同步程式碼。 在與用來建立或取用 .NET 工作的 .NET 程式庫進行大量交互操作時,建議最好使用工作運算式。 在 F# 中撰寫大部分非同步程式碼時,建議最好使用 F# 非同步運算式,因為這些運算式更簡潔且更複合,還能避免與 .NET 工作相關的特定警告。
語法
async { expression }
備註
先前的語法將 expression
所代表的計算設定為以非同步的方式執行,也就是在執行非同步睡眠作業、I/O 和其他非同步作業時,不會封鎖目前的計算執行緒。 非同步計算通常會在背景執行緒上啟動,同時也會在目前的執行緒上繼續執行。 運算式的型別為 Async<'T>
,其中 'T
是使用 return
關鍵字時,運算式所傳回的型別。
Async
類別提供支援多種案例的方法。 一般的做法是,先建立表示計算的 Async
物件或是您想要執行的非同步計算,然後使用其中一個觸發函式來啟動這些計算。 您使用的觸發程序取決於您是否想要使用目前的執行緒、背景執行緒或 .NET 工作物件。 例如,如果要在目前的執行緒上啟動非同步計算,您可以使用 Async.StartImmediate
。 當您在使用介面執行緒上啟動非同步計算時,不會封鎖處理使用者動作的主要事件迴圈,例如按鍵和滑鼠的活動,因此您的應用程式會保持有回應的狀態。
使用 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!
只能用來直接等候 F# 非同步計算 Async<T>
。 您可以間接等候其他類型的非同步作業:
- 透過
Async.AwaitTask
將 .NET 工作、Task<TResult> 和非泛型 Task 合併在一起 - 透過
.AsTask()
和Async.AwaitTask
將 .NET 值工作、ValueTask<TResult> 和非泛型 ValueTask 合併在一起 - 透過
task { return! expr } |> Async.AwaitTask
將符合 F# RFC FS-1097 所指定的「GetAwaiter」模式的任何物件合併在一起。
控制流程
非同步運算式可以包含控制流程建構,例如 for .. in .. do
、while .. do
、try .. with ..
、try .. finally ..
、if .. then .. else
,和 if .. then ..
。 這些可能又包含進一步的非同步建構,但 with
和 finally
處理常式除外,因為這兩者會同步執行。
F# 非同步運算式不支援非同步 try .. finally ..
。 您可以在此案例中使用工作運算式。
use
和 use!
繫結
在非同步運算式中,use
繫結可以繫結至型別 IDisposable 的值。 針對後者會以非同步的方式執行處置清除作業。
除了 let!
以外,您還可以使用 use!
來執行非同步繫結。 let!
和 use!
間的差異與 let
和 use
間的差異相同。 針對 use!
,物件會在目前範圍關閉時遭處置。 請注意,在 F# 目前的版本中,use!
不允許將值初始化為 null,即使 use
這樣做也一樣。
非同步基本類型
執行單一非同步工作並傳回結果的方法稱為非同步基本類型。這些方法是專為搭配 let!
使用而設計的。 F# 核心程式庫中定義了多種非同步基本類型。 模組 FSharp.Control.WebExtensions
中定義了兩種用於 Web 應用程式的方法:WebRequest.AsyncGetResponse
和 WebClient.AsyncDownloadString
。 這兩個基本類型都會從網頁下載資料並指定 URL。 AsyncGetResponse
會產生一個 System.Net.WebResponse
物件,且 AsyncDownloadString
會產生一個代表網頁 HTML 的字串。
FSharp.Control.CommonExtensions
模組中包含多個用於非同步 I/O 作業的基本類型。 這些 System.IO.Stream
類別的擴充方法為 Stream.AsyncRead
和 Stream.AsyncWrite
。
您也可以透過定義主體為非同步運算式的函式或方法,來撰寫自己的非同步基本類型。
若要在 F# 非同步程式設計模型中使用設計給其他非同步模型的 .NET Framework 非同步方法,您需要建立一個會傳回 F# Async
物件的函式。 F# 程式庫有可讓您輕鬆執行的函式。
此處包含使用非同步運算式的其中一個範例,針對 Async 類別方法,文件中還有許多其他範例。
此範例會示範如何使用非同步運算式平行執行程式碼。
在下列程式碼範例中,函式 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", "http://www.microsoft.com/"
"MSDN", "http://msdn.microsoft.com/"
"Bing", "http://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()