非同步運算式

本文說明 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! 繫結的右側後,非同步運算式的其餘部分會繼續執行。

下列程式碼顯示了 letlet! 間的差異。 使用 let 的程式碼只會建立非同步計算來作為物件。舉例來說,您可以稍後使用 Async.StartImmediateAsync.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 .. dowhile .. dotry .. with ..try .. finally ..if .. then .. else,和 if .. then ..。 這些可能又包含進一步的非同步建構,但 withfinally 處理常式除外,因為這兩者會同步執行。

F# 非同步運算式不支援非同步 try .. finally ..。 您可以在此案例中使用工作運算式

useuse! 繫結

在非同步運算式中,use 繫結可以繫結至型別 IDisposable 的值。 針對後者會以非同步的方式執行處置清除作業。

除了 let! 以外,您還可以使用 use! 來執行非同步繫結。 let!use! 間的差異與 letuse 間的差異相同。 針對 use!,物件會在目前範圍關閉時遭處置。 請注意,在 F# 目前的版本中,use! 不允許將值初始化為 null,即使 use 這樣做也一樣。

非同步基本類型

執行單一非同步工作並傳回結果的方法稱為非同步基本類型。這些方法是專為搭配 let! 使用而設計的。 F# 核心程式庫中定義了多種非同步基本類型。 模組 FSharp.Control.WebExtensions 中定義了兩種用於 Web 應用程式的方法:WebRequest.AsyncGetResponseWebClient.AsyncDownloadString。 這兩個基本類型都會從網頁下載資料並指定 URL。 AsyncGetResponse 會產生一個 System.Net.WebResponse 物件,且 AsyncDownloadString 會產生一個代表網頁 HTML 的字串。

FSharp.Control.CommonExtensions 模組中包含多個用於非同步 I/O 作業的基本類型。 這些 System.IO.Stream 類別的擴充方法為 Stream.AsyncReadStream.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()

另請參閱