異步表達式
本文說明 F# 對於異步表達式的支援。 異步表達式提供以異步方式執行計算的一種方式,也就是不封鎖其他工作的執行。 例如,異步計算可用來撰寫具有使用者界面 (UI) 的應用程式,這些應用程式在執行其他工作時仍能回應使用者。 F# 異步工作流程程式設計模型 可讓您撰寫功能程式,同時隱藏連結庫中線程轉換的詳細數據。
您也可以使用 任務表達式撰寫非同步程式碼,直接建立 .NET 任務。 當與建立或使用 .NET 任務的 .NET 程式庫廣泛互操作時,最好使用任務表達式。 在 F# 中撰寫大部分異步程式代碼時,最好使用 F# 異步運算式,因為它們更簡潔、更組合,並避免與 .NET 工作相關聯的某些注意事項。
語法
async { expression }
備註
在先前的語法中,expression
所代表的計算會設定為異步執行,也就是說,在異步睡眠作業、I/O 和其他異步操作執行時,不會封鎖目前的計算線程。 異步計算通常會在背景線程上啟動,同時在目前線程上繼續執行。 表達式的類型為 Async<'T>
,其中 'T
是使用 return
關鍵詞時表達式所傳回的類型。
Async
類別提供支持數個案例的方法。 一般方法是建立 Async
物件,代表您想要以異步方式執行的計算或計算,然後使用其中一個觸發函式啟動這些計算。 您使用的觸發程式取決於您是否要使用目前線程、背景線程或 .NET 工作物件。 例如,若要在目前線程上啟動異步計算,您可以使用 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!
只能用來直接等待 F# 非同步計算 Async<T>
。 您可以間接等候其他類型的異步操作:
- 結合
Async.AwaitTask
、.NET 工作中的 Task<TResult> 和非泛型的 Task - .NET 值工作、ValueTask<TResult> 和非泛型 ValueTask,透過結合
.AsTask()
和Async.AwaitTask
- 在 F# RFC FS-1097中指定的“GetAwaiter”模式之後的任何物件,結合
task { return! expr } |> Async.AwaitTask
。
控制流
異步表達式可以包含控制流程建構,例如 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# 核心連結庫中定義了數個異步基本類型。 有兩種此類方法適用於 Web 應用程式,定義於模組 FSharp.Control.WebExtensions
:WebRequest.AsyncGetResponse
和 HttpClient.GetStringAsync(封裝於 Async.AwaitTask
,以相容於 F# 的異步模型)。 這兩種工具都會在提供URL後從網頁下載資料。
AsyncGetResponse
會產生 System.Net.WebResponse
物件,而 GetStringAsync
會產生代表網頁 HTML 的字串。
異步 I/O 作業的數個基本類型會包含在 FSharp.Control.CommonExtensions
模組中。
System.IO.Stream
類別的擴充方法包括 Stream.AsyncRead
和 Stream.AsyncWrite
。
您也可以透過定義主體為異步表達式的函式或方法,撰寫自己的異步原語。
若要在 .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
open System.Net.Http
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 httpClient = new HttpClient()
let! html = httpClient.GetStringAsync(uri) |> Async.AwaitTask
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()