標籤:
C#編程總結(六)非同步編程 1、什麼是非同步?
非同步作業通常用於執行完成時間可能較長的任務,如開啟大檔案、串連遠端電腦或查詢資料庫。非同步作業在主應用程式線程以外的線程中執行。應用程式調用方法非同步執行某個操作時,應用程式可在非同步方法呼叫執行其任務時繼續執行。
2、同步與非同步區別
同步(Synchronous):在執行某個操作時,應用程式必須等待該操作執行完成後才能繼續執行。
非同步(Asynchronous):在執行某個操作時,應用程式可在非同步作業執行時繼續執行。實質:非同步作業,啟動了新的線程,主線程與方法線程並存執行。
3、非同步和多線程的區別
我們已經知道,非同步實質是開啟了新的線程。它與多線程的區別是什麼呢?
簡單的說就是:非同步線程是由線程池負責管理,而多線程,我們可以自己控制,當然在多線程中我們也可以使用線程池。
就拿網路扒蟲而言,如果使用非同步模式去實現,它使用線程池進行管理。非同步作業執行時,會將操作丟給線程池中的某個背景工作執行緒來完成。當開始I/O操作的時候,非同步會將背景工作執行緒還給線程池,這意味著擷取網頁的工作不會再佔用任何CPU資源了。直到非同步完成,即擷取網頁完畢,非同步才會通過回調的方式通知線程池。可見,非同步模式藉助於線程池,極大地節約了CPU的資源。
註:DMA(Direct Memory Access)直接記憶體存取,顧名思義DMA功能就是讓裝置可以繞過處理器,直接由記憶體來讀取資料。通過直接記憶體存取的資料交換幾乎可以不損耗CPU的資源。在硬體中,硬碟、網卡、音效卡、顯卡等都有直接記憶體存取功能。非同步編程模型就是讓我們充分利用硬體的直接記憶體存取功能來釋放CPU的壓力。
兩者的應用情境:
計算密集型工作,採用多線程。
IO密集型工作,採用非同步機制。
4、非同步應用
.NET Framework 的許多方面都支援非同步編程功能,這些方麵包括:
1)檔案 IO、流 IO、通訊端 IO。
2)網路。
3)遠端通道(HTTP、TCP)和代理。
4)使用 ASP.NET 建立的 XML Web services。
5)ASP.NET Web Form。
6)使用 MessageQueue 類的訊息佇列。
.NET Framework 為非同步作業提供兩種設計模式:
1)使用 IAsyncResult 對象的非同步作業。
2)使用事件的非同步作業。
IAsyncResult 設計模式允許多種編程模型,但更加複雜不易學習,可提供大多數應用程式都不要求的靈活性。可能的話,類庫設計者應使用事件驅動模型實現非同步方法呼叫。在某些情況下,庫設計者還應實現基於 IAsyncResult 的模型。
使用 IAsyncResult 設計模式的非同步作業是通過名為 Begin操作名稱和End操作名稱的兩個方法來實現的,這兩個方法分別開始和結束非同步作業操作名稱。例如,FileStream 類提供 BeginRead 和 EndRead 方法來從檔案非同步讀取位元組。這兩個方法實現了 Read 方法的非同步版本。在調用 Begin操作名稱後,應用程式可以繼續在調用線程上執行指令,同時非同步作業在另一個線程上執行。每次調用 Begin操作名稱 時,應用程式還應調用 End操作名稱來擷取操作的結果。Begin操作名稱 方法開始非同步作業操作名稱並返回一個實現 IAsyncResult 介面的對象。 .NET Framework 允許您非同步呼叫任何方法。定義與您需要調用的方法具有相同簽名的委託;公用語言運行庫將自動為該委託定義具有適當簽名的 BeginInvoke 和 EndInvoke 方法。
IAsyncResult Object Storage Service有關非同步作業的資訊。下表提供了有關非同步作業的資訊。
名稱 |
說明 |
AsyncState |
擷取使用者定義物件,它限定或包含關於非同步作業的資訊。 |
AsyncWaitHandle |
擷取用於等待非同步作業完成的 WaitHandle。 |
CompletedSynchronously |
擷取一個值,該值指示非同步作業是否同步完成。 |
IsCompleted |
擷取一個值,該值指示非同步作業是否已完 |
5、應用執行個體
案例1-讀取檔案
通常讀取檔案是一個比較耗時的工作,特別是讀取大檔案的時候,常見的上傳和下載。但是我們又不想讓使用者一直等待,使用者同樣可以進行其他動作,可以使得系統有良好的互動性。這裡我們寫了同步調用和非同步呼叫來進行比較說明。
讀取檔案類
using System;using System.IO;using System.Threading;namespace AsynSample{ class FileReader { /// <summary> /// 緩衝池 /// </summary> private byte[] Buffer { get; set; } /// <summary> /// 緩衝區大小 /// </summary> public int BufferSize { get; set; } public FileReader(int bufferSize) { this.BufferSize = bufferSize; this.Buffer = new byte[BufferSize]; } /// <summary> /// 同步讀取檔案 /// </summary> /// <param name="path">檔案路徑</param> public void SynsReadFile(string path) { Console.WriteLine("同步讀取檔案 begin"); using (FileStream fs = new FileStream(path, FileMode.Open)) { fs.Read(Buffer, 0, BufferSize); string output = System.Text.Encoding.UTF8.GetString(Buffer); Console.WriteLine("讀取的檔案資訊:{0}",output); } Console.WriteLine("同步讀取檔案 end"); } /// <summary> /// 非同步讀取檔案 /// </summary> /// <param name="path"></param> public void AsynReadFile(string path) { Console.WriteLine("非同步讀取檔案 begin"); //執行Endread時報錯,fs已經釋放,注意在非同步中不能使用釋放需要的資源 //using (FileStream fs = new FileStream(path, FileMode.Open)) //{ // Buffer = new byte[BufferSize]; // fs.BeginRead(Buffer, 0, BufferSize, AsyncReadCallback, fs); //} if (File.Exists(path)) { FileStream fs = new FileStream(path, FileMode.Open); fs.BeginRead(Buffer, 0, BufferSize, AsyncReadCallback, fs); } else { Console.WriteLine("該檔案不存在"); } } /// <summary> /// /// </summary> /// <param name="ar"></param> void AsyncReadCallback(IAsyncResult ar) { FileStream stream = ar.AsyncState as FileStream; if (stream != null) { Thread.Sleep(1000); //讀取結束 stream.EndRead(ar); stream.Close(); string output = System.Text.Encoding.UTF8.GetString(this.Buffer); Console.WriteLine("讀取的檔案資訊:{0}", output); } } }}
測試案例
using System;using System.Threading;namespace AsynSample{ class Program { static void Main(string[] args) { FileReader reader = new FileReader(1024); //改為自己的檔案路徑 string path = "C:\\Windows\\DAI.log"; Console.WriteLine("開始讀取檔案了..."); //reader.SynsReadFile(path); reader.AsynReadFile(path); Console.WriteLine("我這裡還有一大灘事呢."); DoSomething(); Console.WriteLine("終於完事了,輸入任意鍵,歇著!"); Console.ReadKey(); } /// <summary> /// /// </summary> static void DoSomething() { Thread.Sleep(1000); for (int i = 0; i < 10000; i++) { if (i % 888 == 0) { Console.WriteLine("888的倍數:{0}",i); } } } }}
輸出結果:
同步輸出:
非同步輸出:
結果分析:
如果是同步讀取,在讀取時,當前線程讀取檔案,只能等到讀取完畢,才能執行以下的操作
而非同步讀取,是建立了新的線程,讀取檔案,而主線程,繼續執行。我們可以開啟工作管理員來進行監視。
案例二--基於委託的非同步作業
系統內建一些類具有非同步呼叫方式,如何使得自訂對象也具有非同步功能呢?
我們可以藉助委託來輕鬆實現非同步。
說到BeginInvoke,EndInvoke就不得不停下來看一下委託的本質。為了便於理解委託,我定義一個簡單的委託:
public delegate string MyFunc(int num, DateTime dt);
我們再來看一下這個委託在編譯後的程式集中是個什麼樣的:
委託被編譯成一個新的類型,擁有BeginInvoke,EndInvoke,Invoke這三個方法。前二個方法的組合使用便可實現非同步呼叫。第三個方法將以同步的方式調用。 其中BeginInvoke方法的最後二個參數用於回調,其它參數則與委託的封裝方法的輸入參數是匹配的。 EndInvoke的傳回值與委託的封裝方法的傳回值匹配。
非同步實現檔案下載:
using System;using System.Text;namespace AsynSample{ /// <summary> /// 下載委託 /// </summary> /// <param name="fileName"></param> public delegate string AysnDownloadDelegate(string fileName); /// <summary> /// 通過委託實現非同步呼叫 /// </summary> class DownloadFile { /// <summary> /// 同步下載 /// </summary> /// <param name="fileName"></param> public string Downloading(string fileName) { string filestr = string.Empty; Console.WriteLine("下載事件開始執行"); System.Threading.Thread.Sleep(3000); Random rand = new Random(); StringBuilder builder =new StringBuilder(); int num; for(int i=0;i<100;i++) { num = rand.Next(1000); builder.Append(i); } filestr = builder.ToString(); Console.WriteLine("下載事件執行結束"); return filestr; } /// <summary> /// 非同步下載 /// </summary> public IAsyncResult BeginDownloading(string fileName) { string fileStr = string.Empty; AysnDownloadDelegate downloadDelegate = new AysnDownloadDelegate(Downloading); return downloadDelegate.BeginInvoke(fileName, Downloaded, downloadDelegate); } /// <summary> /// 非同步下載完成後事件 /// </summary> /// <param name="result"></param> private void Downloaded(IAsyncResult result) { AysnDownloadDelegate aysnDelegate = result.AsyncState as AysnDownloadDelegate; if (aysnDelegate != null) { string fileStr = aysnDelegate.EndInvoke(result); if (!string.IsNullOrEmpty(fileStr)) { Console.WriteLine("下載檔案:{0}", fileStr); } else { Console.WriteLine("下載資料為空白!"); } } else { Console.WriteLine("下載資料為空白!"); } } }}
通過案例,我們發現,使用委託能夠很輕易的實現非同步。這樣,我們就可以自訂自己的非同步作業了。
C#編程總結(六)非同步編程