C#編程總結(六)異步編程

C#編程總結(六)異步編程 

一、什麼是異步?

異步操做一般用於執行完成時間可能較長的任務,如打開大文件、鏈接遠程計算機或查詢數據庫。異步操做在主應用程序線程之外的線程中執行。應用程序調用方法異步執行某個操做時,應用程序可在異步方法執行其任務時繼續執行。數據庫

二、同步與異步的區別

同步(Synchronous):在執行某個操做時,應用程序必須等待該操做執行完成後才能繼續執行。編程

異步(Asynchronous):在執行某個操做時,應用程序可在異步操做執行時繼續執行。實質:異步操做,啓動了新的線程,主線程與方法線程並行執行。設計模式

三、異步和多線程的區別    

      咱們已經知道,異步的實質是開啓了新的線程。它與多線程的區別是什麼呢?緩存

      簡單的說就是:異步線程是由線程池負責管理,而多線程,咱們能夠本身控制,固然在多線程中咱們也可使用線程池。網絡

      就拿網絡扒蟲而言,若是使用異步模式去實現,它使用線程池進行管理。異步操做執行時,會將操做丟給線程池中的某個工做線程來完成。當開始I/O操做的時候,異步會將工做線程還給線程池,這意味着獲取網頁的工做不會再佔用任何CPU資源了。直到異步完成,即獲取網頁完畢,異步纔會經過回調的方式通知線程池。可見,異步模式藉助於線程池,極大地節約了CPU的資源。多線程

      注:DMA(Direct Memory Access)直接內存存取,顧名思義DMA功能就是讓設備能夠繞過處理器,直接由內存來讀取資料。經過直接內存訪問的數據交換幾乎能夠不損耗CPU的資源。在硬件中,硬盤、網卡、聲卡、顯卡等都有直接內存訪問功能。異步編程模型就是讓咱們充分利用硬件的直接內存訪問功能來釋放CPU的壓力。
      二者的應用場景:dom

      計算密集型工做,採用多線程。
      IO密集型工做,採用異步機制。異步

四、異步應用

.NET Framework 的許多方面都支持異步編程功能,這些方面包括:
      1)文件 IO、流 IO、套接字 IO。
      2)網絡。
      3)遠程處理信道(HTTP、TCP)和代理。
      4)使用 ASP.NET 建立的 XML Web services。
      5)ASP.NET Web 窗體。
      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 對象存儲有關異步操做的信息。下表提供了有關異步操做的信息。

名稱        

說明

AsyncState

獲取用戶定義的對象,它限定或包含關於異步操做的信息。

AsyncWaitHandle

獲取用於等待異步操做完成的 WaitHandle。

CompletedSynchronously

獲取一個值,該值指示異步操做是否同步完成。

IsCompleted

獲取一個值,該值指示異步操做是否已完

五、應用實例

案例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("下載數據爲空!");
            }
        }
    }
}

經過案例,咱們發現,使用委託可以很輕易的實現異步。這樣,咱們就能夠自定義本身的異步操做了。

相關文章
相關標籤/搜索