三種異步編程模型


Net 中很多的類接口設計的時候都考慮了多線程問題,簡化了多線程程序的開發。 不用自己去寫 WaitHandler 等這些底層的代碼。由於歷史的發展,這些類的接口設計有着三種不同的風格: EAP(*)
APM(*)TPL。目前重點用 TPL

 

EAP


EAP Event-based Asynchronous Pattern( 基於事件的異步模型) 的簡寫, 類似於 Ajax 中的XmlHttpRequestsend 之后並不是處理完成了,而是在 onreadystatechange 事件中再通知處理完成。

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }    

        private void button1_Click(object sender, EventArgs e)
        {
         
            WebClient client = new WebClient();
            client.DownloadStringCompleted += Client_DownloadStringCompleted;
            client.DownloadStringAsync(new Uri("http://www.baidu.com")); ;

        }

        private void Client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            MessageBox.Show(e.Result);
        }
    }

優點是簡單,缺點是當實現復雜的業務的時候很麻煩,比如下載 A 成功后再下載 b,如果下載 b成功再下載 c,否則就下載 d
EAP 的類的特點是:一個異步方法配一個***Completed 事件。 .Net 中基於 EAP 的類比較少。也有更好的替代品,因此了解即可。

 

AMP


APM(Asynchronous Programming Model).Net 舊版本中廣泛使用的異步編程模型。使用了 APM的異步方法會返回一個 IAsyncResult 對象, 這個對象有一個重要的屬性 AsyncWaitHandle, 他是一個
用來等待異步任務執行結束的一個同步信號。

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            FileStream fs = File.OpenRead("d:/1.txt");
            byte[] buffer = new byte[16];
            IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
            aResult.AsyncWaitHandle.WaitOne();//等待任務執行結束
            MessageBox.Show(Encoding.UTF8.GetString(buffer));
            fs.EndRead(aResult);
        }
    }

如果不加 aResult.AsyncWaitHandle.WaitOne() 那么很有可能打印出空白,因為 BeginRead只是“開始讀取”。調用完成一般要調用 EndXXX 來回收資源。APM 的特點是:方法名字以 BeginXXX 開頭,返回類型為 IAsyncResult, 調用結束后需要EndXXX
.Net 中有如下的常用類支持 APMStreamSqlCommandSocket 等。
APM 還是太復雜,了解即可。

 

TPL


TPLTask Parallel Library)是.Net 4.0 之后帶來的新特性,更簡潔,更方便。現在在.Net平台下已經大面積使用。

  private void button1_Click(object sender, EventArgs e)
        {
            FileStream fs = File.OpenRead("d:/1.txt");
            byte[] buffer = new byte[16];
            Task<int> task = fs.ReadAsync(buffer, 0, buffer.Length);
            task.Wait();
            MessageBox.Show("讀取了" + task.Result + "個字節");
            MessageBox.Show(Encoding.UTF8.GetString(buffer));
        }

使用async和awit關鍵字

 private async void button2_Click(object sender, EventArgs e)
        {
            FileStream fs = File.OpenRead("d:/1.txt");
            byte[] buffer = new byte[16];
            int len = await fs.ReadAsync(buffer, 0, buffer.Length);
            MessageBox.Show("讀取了" + len + "個字節");
            MessageBox.Show(Encoding.UTF8.GetString(buffer));
        }

注意方法中如果有 await,則方法必須標記為 async,不是所有方法都可以被輕松的標記為 asyncWinForm 中的事件處理方法都可以標記為 asyncMVC 中的 Action 方法也可以標記為 async、控制台的 Main 方法不能標記為 async
TPL 的特點是:方法都以 XXXAsync 結尾,返回值類型是泛型的 Task<T>
TPL 讓我們可以用線性的方式去編寫異步程序,不再需要像 EAP 中那樣搞一堆回調、邏輯跳來跳去了。 await 現在已經被 JavaScript 借鑒走了!

 
await 實現“先下載 A,如果下載的內容長度大於 100 則下載 B,否則下載 C”就很容易了
再看看 WebClient TPL 用法:

private async void button3_Click(object sender, EventArgs e)
        {
            var wc = new WebClient();
            string html = await wc.DownloadStringTaskAsync("http://www.baidu.com");//不要丟了 await
            MessageBox.Show(html);
            //上面的代碼並不是完全等價於
            WebClient wc1 = new WebClient();
            var task = wc1.DownloadStringTaskAsync("http://www.baidu.com");
            task.Wait();
            MessageBox.Show(task.Result);

            //  因為如果按照上面的寫法,會卡死 UI 線程
            //而 await 則 不 會 。。。 好 像 不 是 ? ? ? 那 只 是 因 為 把 html 這 么 長 的 字 符 串
            //MessageBox.Show 很慢, MessageBox.Show(html.Substring(10)); 就證明了這一點


            // Task<T> 中的 T 是什么類型每個方法都不一樣,要看文檔。
            //WebClient、 Stream、 Socket 等這些“歷史悠久”的類都同時提供了 APM、 TPL 風格的
            //API,甚至有的還提供了 EAP 風格的 API。盡可能使用 TPL 風格的
        }

編寫異步方法

返回值為 Task<T>,潛規則(不要求)是方法名字以 Async 結尾:

 static Task<string> F2Async()
        {
            return Task.Run(() =>
            {
                System.Threading.Thread.Sleep(2000);
                return "F2";
            });
        }

調用:

 private async void button4_Click(object sender, EventArgs e)
        {
            string s = await F2Async();
            MessageBox.Show(s);

        }

 

TPL寫法


 Task.Run()一個用來把一個代碼段包裝為 Task<T>的方法 Run 中委托的代碼體就是異步任務執行的邏輯,最后 return 返回值。

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            /*  string i1 = await F1Async();
              MessageBox.Show("i1=" + i1);
              string i2 = await F2Async();
              MessageBox.Show("i2=" + i2);
              */

            //Task.Run()一個用來把一個代碼段包裝為 Task<T>的方法 Run 中委托的代碼體就是異步任
            //務執行的邏輯,最后 return 返回值。
            //把 button1_click 改成:
            Task<string> task1 = F1Async();
            Task<string> task2 = F2Async();
            string i1 = await task1;
            MessageBox.Show("i1=" + i1);
            string i2 = await task2;
            MessageBox.Show("i2=" + i2);

        }
        static Task<string> F1Async()
        {
            MessageBox.Show("F1 Start");
            return Task.Run(() =>
            {
                System.Threading.Thread.Sleep(1000);
                MessageBox.Show("F1 Run");
                return "F1";
            });
        }
        static Task<string> F2Async()
        {
            MessageBox.Show("F2 Start");
            return Task.Run(() =>
            {
                System.Threading.Thread.Sleep(2000);
                MessageBox.Show("F2 Run");
                return "F2";
            });
        }
   
    }

使用async返回值和Task.Run()返回值

 static Task<string> GetRuPengAsync()
        {
            return Task.Run(() =>
            {
                return "a";
            });
            //這里可以return Task<string> 類型的值,不能return “a”;
        }
        static async Task<string> GetRuPengAsync1()
        {
            return  "a";
            //這里不可以return Task<string> 類型的值, 而可以能return “a”;
        }

1、 只要方法是 Task<T>類型的返回值,都可以用 await 來等待調用獲取返回值
2、 如果一個返回 Task<T>類型的方法被標記了 async,那么只要方法內部直接 return T 這個類型的實例就可以。
3、 一個返回 Task<T>類型的方法沒有被標記了 async,那么需要方法內部直接 Task 實例

   private async void button2_Click(object sender, EventArgs e)
        {
            //int i = await F1Async();
            int i = await M2Async();
            MessageBox.Show(i.ToString());
        }

        static Task<int> M1Async()
        {
            return Task.Run(() => {
                return 2;
            });
        }
        static async Task<int> M2Async()
        {
            return 2;
        }

 

TPL高級用法


1、 如果方法內部有 await,則方法必須標記為 asyncasp.net mvc ActionWinForm 的事件處理函數都可以標記 async,控制台 Main 不能 async。對於不能標記為怎么辦?
  F1Async().Result 注意有的上下文下會有死鎖。
2、 如果返回值就是一個立即可以隨手可得的值,那么就用 Task.FromResult()如果是一個需要休息一會的任務(比如下載失敗則過 5 秒鍾后重試。主線程不休息,和Thread.Sleep 不一樣),

  那么就用 Task.Delay()
3Task.Factory.FromAsync()IAsyncResult 轉換為 Task, 這樣 APM 風格的 api 也可以用 await 來調用。
4、 編寫異步方法的簡化寫法。如果方法聲明為 async,那么可以直接 return 具體的值,不再用創建Task,由編譯器創建 Task

        static async Task<int> F1Async()
        {
            return 1;
        }

        //2、 如果返回值就是一個立即可以隨手可得的值,那么就用 Task.FromResult()
        static Task<int> F2Async()
        {
            return Task.FromResult(3);
        }
static Task<int> F3Async() { return Task.Run(() => { return 1 + 3; }); }

WinForm 程序依次下載三個網址: Task.WaitAll(task1, task2, task3);Task.WaitAll 是等待所有任務完成:

private async void button2_Click(object sender, EventArgs e)
        {
            /* HttpClient wc = new HttpClient();
             string s1 = await wc.GetStringAsync(textBox1.Text);
             label1.Text = s1.Length.ToString();
             string s2 = await wc.GetStringAsync(textBox2.Text);
             label2.Text = s2.Length.ToString();
             string s3 = await wc.GetStringAsync(textBox3.Text);
             label3.Text = s3.Length.ToString(); */


            HttpClient hc = new HttpClient();
            var task1 = hc.GetStringAsync(textBox1.Text);
            var task2 = hc.GetStringAsync(textBox2.Text);
            var task3 = hc.GetStringAsync(textBox3.Text);
            Task.WaitAll(task1, task2, task3);
            label1.Text = task1.Result.Length.ToString();
            label2.Text = task2.Result.Length.ToString();
            label3.Text = task3.Result.Length.ToString();

        }

 

TPL異常處理


1TPL 中,如果程序中出現異常,除非進行 try...catch,否則有可能是感覺不到出了異常的。測試,把上面下載程序的域名改成一個不存在的域名。
2TPL 程序有時候還會拋出 AggregateException, 這通常發生在並行有多個任務執行的情況下。 比如:

private async void button3_Click(object sender, EventArgs e)
        {
            try
            {
                HttpClient hc = new HttpClient();
                var task1 = hc.GetStringAsync(textBox1.Text);
                var task2 = hc.GetStringAsync(textBox2.Text);
                var task3 = hc.GetStringAsync(textBox3.Text);
                Task.WaitAll(task1, task2, task3);
                label1.Text = task1.Result.Length.ToString();
                label2.Text = task2.Result.Length.ToString();
                label3.Text = task3.Result.Length.ToString();
            }
            catch (AggregateException ae)
            {
                MessageBox.Show(ae.GetBaseException().ToString());
            }
        }

因為多個並行的任務可能有多個有異常,因此會包裝為 AggregateException 異常,AggregateException InnerExceptions 屬性可以獲得多個異常對象信息


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM