非同步編程(Asynchronous Programming)

來源:互聯網
上載者:User

非同步編程(Asynchronous Programming)

 

非同步編程與我們所看過的其他並行編程形式有一些不同,討論的其他主題可以有大量的線程並行運行,可以完全利用系統中可用的處理器;而在非同步編程中,需要避免阻塞線程,我們在這一章的第一節“線程、記憶體、鎖定和阻塞”中已經對阻塞線程的概念有所瞭解了。阻塞的線程是不能工作的線程,因為它需要等待其他任務的完成;線程等待的通常任務是作業系統執行的輸入輸出,但有時也可能是等待鎖,因此會進行臨界區。線程是相對昂貴的資源,每個線程分配 1 MB 的棧(stack),以及作業系統核心為管理大量線程而產生的其他相關消耗。在效能至上的代碼中,保持阻塞線程數量在較低的水平上是至關重要的;理論上,只要做到有多少的處理器,就有多少的線程,就不會有阻塞的線程了。

 

注意

為了概要地瞭解一下用這些方法能夠達到什麼樣的結果,可以看一下Amanda Laucher 2009 年在 Lang.NET 上的演講,她講解了如何使用F# 工作流程並行化 C# 的程式,以及實現了一些令人印象深刻的結果:www.langnetsymposium.com/2009/speakers.aspx。

 

在這一節,我們將學習如何使用 .NET 架構的非同步編程模型(asynchronous programming model)避免輸入輸出期間線程的阻塞。非同步編程模型的意思是,在有關流的類上,使用一對Begin/End 方法,比如 BeginRead/EndRead;典型地,這一對方法執行某種輸入輸出任務,比如讀檔案。這種編程方法的名聲很不好,因為需要找到能在Begin/End 調用之間保持狀態的好方法。這一節我們直接討論編程模型,相反,將看一下 F# 的一個功能,非同步工作流程(asynchronous workflows),看如何用它來避免在其他 .NET 語言中與使用非同步編程模型相關的工作。為了更詳細瞭解非同步編程模型,以及使用的困難,請參考Jeffrey Richter 在 MSDN 上的文章《Asynchronous Device Operations》(http://msdn.microsoft.com/en-us/magazine/cc163415.aspx)。

非同步工作流程不是由 .NET 的非同步編程模型所專用。在下一節“訊息傳遞”中,我們將學習如何使用這些工作流程與 F# 的郵箱(mailboxes)來協調大量不同的任務,它可以等待任務完成而不阻塞線程。

瞭解 F# 中非同步工作流程的第一步是瞭解它的文法。建立非同步工作流程,使用一元文法(monadic syntax),同我們在第三章中見過的序列運算式相似;基本文法使用關鍵字async,加用大括弧括起來的工作流程運算式:async { ... }。簡單的工作流程序像這樣使用工作流程:

 

open System.IO

 

// a function to read a text fileasynchronusly

let readFile file =

  async{ let! stream = File.AsyncOpenText(file)

         let! fileContents = stream.AsyncReadToEnd()

         return fileContents }

 

// create an instance of the workflow

let readFileWorkflow = readFile"mytextfile.txt"

 

// invoke the workflow and get the contents

let fileContents = Async.RunSynchronouslyreadFileWorkflow

 

編譯這個程式,需要引用 FSharp.PowerPack.dll。程式中的 readFile 函數建立一個工作,非同步讀檔案,然後返迴文件的內容;接下來,建立工作流程的執行個體 readFileWorkflow;最後,運行這個工作流程,獲得檔案的內容。很重要的一點,是要理解,只調用 readFile 函數並不真正讀檔案;相反,它報建工作流程的新執行個體,然後,運行這個工作流程,去執行讀檔案的任務;Async.RunSynchronously 函數真正負責運行工作流程。工作流程執行個體是一種小型的資料結構,有點像一段小程式,能夠解釋一些要做的工作。

關注這個樣本最重要的是 let 後面的驚嘆號(let!),通常讀作let bang。工作流程/一元文法可以為 let! 賦予不同的含義。在非同步工作流程中,它表示將要發生的非同步作業;在非同步作業發生期間工作流程停止運行;線上程池中插入一個回呼函數,當這個非同步作業完成時被調用,如果發出原始調用的線程沒有空閑,就可能發生在不同的線程上;非同步呼叫之後,原始線程被釋放,可以繼續其他工作。

你可能已經注意到,let! 是用在有 Async首碼的一些專用方法,在 FSharp.PowerPack.dll 中,這些函數被定義成類型擴增(type augmentations),它與 C# 的擴充方法(extension

methods)等價,這些方法處理對 Begin/End方法對的調用。如果沒有現成的 Async 方法,我們自己建立也很簡單,使用 Async.Primitive 函數和 Begin/End 方法對。

簡單的步驟可能像這樣:

第一步:主程式線程啟動開啟檔案流的進程,線上程池中插入回呼函數,當這個操作完成時使用,而這個線程現在空閑可以繼續做其他工作;

第二步:當檔案流已經開啟,線程池線程(A thread pool thread)被啟用,開始讀檔案的內容,線上程池中插入回呼函數,當這個操作完成時使用。因為它是一個線程池線程,所以,它將返回到線程池;

第三步:當已經完成讀檔案,線程池線程被啟用,將返回從檔案中讀到的文本資料,並返回到線程池;

第四步:因為我們已經使用了 sync.RunSynchronously 函數,主程式線程將等待工作流程的結果,接收檔案的內容。

在這個簡單的樣本中,你還可能會發現一點缺陷,沒有阻塞主程式線程等待輸入輸出,但是,因為我們等待非同步工作流程完成,也就阻塞了主程式線程,直到輸入輸出完成。換一種方式,在它自己的[ 線程嗎? ]上運行非同步工作流程並等待結果,就沒有或幾乎沒有優勢了。然而,並行運行幾個工作流程相當簡單;同時運行幾個工作流程有一個明顯的優勢,因為原始線程在它啟動了第一個非同步任務之後,不會被阻塞,就是說,它是閒置,可以繼續運行其他非同步任務。

要說明這個也很簡單,我們把原來的樣本作一點修改,不是讀一個檔案,而是讀三個檔案。而把這個與同步版本的程式作一下比較,有助於發現它們之間的差別。我們先看一下同步版本:

 

open System

open System.IO

open System.Threading

 

let print s =

  lettid = Thread.CurrentThread.ManagedThreadId

  Console.WriteLine(sprintf"Thread %i: %s" tid s)

 

let readFileSync file =

  print(sprintf "Beginning file %s" file)

  letstream = File.OpenText(file)

  letfileContents = stream.ReadToEnd()

  print(sprintf "Ending file %s" file)

  fileContents

 

// invoke the workflow and get the contents

let filesContents =

  [|readFileSync "text1.txt";

     readFileSync"text2.txt";

     readFileSync"text3.txt"; |]

 

這個程式相當簡單,其中還有一些調試代碼,顯示處理檔案的開始與結束。現在再看一下非同步版本:

 

open System

open System.IO

open System.Threading

 

let print s =

  lettid = Thread.CurrentThread.ManagedThreadId

  Console.WriteLine(sprintf"Thread %i: %s" tid s)

 

// a function to read a text fileasynchronusly

let readFileAsync file =

  async{ do print (sprintf "Beginning file %s" file)

         let! stream = File.AsyncOpenText(file)

         let! fileContents = stream.AsyncReadToEnd()

         do print (sprintf "Ending file %s" file)

         return fileContents }

 

let filesContents =

  Async.RunSynchronously

    (Async.Parallel[ readFileAsync "text1.txt";

                     readFileAsync "text2.txt";

                     readFileAsync "text3.txt"; ])

 

另外,這個版本也包含了一些調試代碼,因此,可以看到程式是如何啟動並執行。最大的改變是現在使用了 Async.Parallel函數,把幾個工作流程組合成一個工作流程。這樣,當第一個線程完成處理第一個非同步呼叫之後,就空閑了,可以繼續處理其他工作流程。看看下面兩個程式的運行結果就知道了:

 

同步結果:

Thread 1: Beginning file text1.txt

Thread 1: Ending file text1.txt

Thread 1: Beginning file text2.txt

Thread 1: Ending file text2.txt

Thread 1: Beginning file text3.txt

Thread 1: Ending file text3.txt

 

非同步結果:

Thread 3: Beginning file text1.txt

Thread 4: Beginning file text2.txt

Thread 3: Beginning file text3.txt

Thread 4: Ending file text2.txt

Thread 4: Ending file text1.txt

Thread 4: Ending file text3.txt

 

兩組結果完全不同。對於同步結果,每一個 Beginning file 後面跟一個 Ending file,且出現在同一個線程中;第二種情況下,所有 Beginningfile 的執行個體同時發生,且在兩個不同的線程中,這是因為每一個線程完成了非同步作業以後,它就空閑了可以繼教啟動另一個操作。輸入輸出一旦完成之後,Ending file 就發生了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.