浅谈.Net异步编程的前世今生----APM篇
前言
在.Net程序開發過程中,我們經常會遇到如下場景:
編寫WinForm程序客戶端,需要查詢數據庫獲取數據,于是我們根據需求寫好了代碼后,點擊查詢,發現界面卡死,無法響應。經過調試,發現查詢數據庫這一步執行了很久,在此過程中,UI被阻塞,無法響應任何操作。
如何解決此問題?我們需要分析問題成因:在WinForm窗體運行時,只有一個主線程,即為UI線程,UI線程在此過程中既負責渲染界面,又負責查詢數據,因此在大量耗時的操作中,UI線程無法及時響應導致出現問題。此時我們需要將耗時操作放入異步操作,使主線程繼續響應用戶的操作,這樣可以大大提升用戶體驗。
直接編寫異步編程也許不是一件輕松的事,和同步編程不同的是,異步代碼并不是始終按照寫好的步驟執行,且如何在異步執行完通知前序步驟也是其中一個問題,因此會帶來一系列的考驗。
幸運的是,在.Net Framework中,提供了多種異步編程模型以及相關的API,這些模型的存在使得編寫異步程序變得容易上手。隨著Framework的不斷升級,相應的模型也在不斷改進,下面我們一起來回顧一下.Net異步編程的前世今生。
第一個異步編程模型:APM
概述
APM,全稱Asynchronous Programing Model,顧名思義,它即為異步編程模型,最早出現于.Net Framework 1.x中。
它使用IAsyncResult設計模式的異步操作,一般由BeginOperationName和EndOperationName兩個方法實現,這兩個方法分別用于開始和結束異步操作,例如FileStream類中提供了BeginRead和EndRead來對文件進行異步字節讀取操作。
使用
在程序運行過程中,直接調用BeginOperationName后,會將所包含的方法放入異步操作,并返回一個IAsyncResult結果,同時異步操作在另外一個線程中執行。
每次在調用BeginOperationName方法后,還應調用EndOperationName方法,來獲取異步執行的結果,下面我們一起來看一個示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks;namespace APMTest {class Program{public delegate void ConsoleDelegate();static void Main(string[] args){ConsoleDelegate consoleDelegate = new ConsoleDelegate(ConsoleToUI);Thread.CurrentThread.Name = "主線程Thread";IAsyncResult ar = consoleDelegate.BeginInvoke(null, null);consoleDelegate.EndInvoke(ar);Console.WriteLine("我是同步輸出,我的名字是:" + Thread.CurrentThread.Name);Console.Read();}public static void ConsoleToUI(){if (Thread.CurrentThread.IsThreadPoolThread){Thread.CurrentThread.Name = "線程池Thread";}else{Thread.CurrentThread.Name = "普通Thread";}Thread.Sleep(3000); //模擬耗時操作Console.WriteLine("我是異步輸出,我的名字是:" + Thread.CurrentThread.Name);}} }在這段示例中,我們定義了一個委托來使用其BeginInvoke/EndInvoke方法用于我們自定義方法的異步執行,同時將線程名稱打印出來,用于區分主線程與異步線程。
如代碼中所示,在調用BeginInvoke之后,立即調用了EndInvoke獲取結果,那么會發生什么呢?
如下圖所示:
看到這里大家也許會比較詫異:為什么同步操作會在異步操作之后輸出呢?這樣不是和同步就一樣了嗎?
原因是這樣的:EndInvoke方法會阻塞調用線程,直到異步調用結束,由于我們在異步操作中模擬了3s耗時操作,所以它會一直等待到3s結束后輸出異步信息,此時才完成了異步操作,進而進行下一步的同步操作。
同時在BeginInvoke返回的IAynscResult中,包含如下屬性:
通過輪詢IsCompleted屬性或使用AsyncWaitHandle屬性,均可以獲取異步操作是否完成,從而進行下一步操作,相關代碼如下所示:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks;namespace APMTest {class Program{public delegate void ConsoleDelegate();static void Main(string[] args){ConsoleDelegate consoleDelegate = new ConsoleDelegate(ConsoleToUI);Thread.CurrentThread.Name = "主線程Thread";IAsyncResult ar = consoleDelegate.BeginInvoke(null, null);//此處改為了輪詢IsCompleted屬性,AsyncWaitHandle屬性同理while (!ar.IsCompleted){Console.WriteLine("等待執行...");}consoleDelegate.EndInvoke(ar);Console.WriteLine("我是同步輸出,我的名字是:" + Thread.CurrentThread.Name);Console.Read();}public static void ConsoleToUI(){if (Thread.CurrentThread.IsThreadPoolThread){Thread.CurrentThread.Name = "線程池Thread";}else{Thread.CurrentThread.Name = "普通Thread";}Thread.Sleep(3000); //模擬耗時操作Console.WriteLine("我是異步輸出,我的名字是:" + Thread.CurrentThread.Name);}} }運行后結果如下:
可以發現,在輪詢屬性時,程序仍然會等待異步操作完成,進而進行下一步的同步輸出,無法達到我們需要的效果,那么究竟有沒有辦法解決呢?
此時我們需要引入一個新方法:使用回調。
在之前的操作中,使用BeginInvoke方法,兩個參數總是傳入的為null。若要使用回調機制,則需傳入一個類型為AsyncCallback的回調函數,并在最后一個參數中,傳入需要使用的參數,如以下代碼所示:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks;namespace APMTest {class Program{public delegate void ConsoleDelegate();static void Main(string[] args){ConsoleDelegate consoleDelegate = new ConsoleDelegate(ConsoleToUI);Thread.CurrentThread.Name = "主線程Thread";//此處傳入AsyncCallback類型的回調函數,并傳入需要使用的參數consoleDelegate.BeginInvoke(CallBack, consoleDelegate);//IAsyncResult ar = consoleDelegate.BeginInvoke(null, null);此處改為了輪詢IsCompleted屬性,AsyncWaitHandle屬性同理//while (!ar.IsCompleted)//{// Console.WriteLine("等待執行...");//}//consoleDelegate.EndInvoke(ar);Console.WriteLine("我是同步輸出,我的名字是:" + Thread.CurrentThread.Name);Console.Read();}public static void ConsoleToUI(){if (Thread.CurrentThread.IsThreadPoolThread){Thread.CurrentThread.Name = "線程池Thread";}else{Thread.CurrentThread.Name = "普通Thread";}Thread.Sleep(3000); //模擬耗時操作Console.WriteLine("我是異步輸出,我的名字是:" + Thread.CurrentThread.Name);}public static void CallBack(IAsyncResult ar){//使用IAsyncResult的AsyncState獲取BeginInvoke中的參數,并用于執行EndInvokeConsoleDelegate callBackDelegate = ar.AsyncState as ConsoleDelegate;callBackDelegate.EndInvoke(ar);}} }運行后結果如下:
此時可以看出,使用回調的方式已經實現了我們需要的效果。在同步執行時,將耗時操作放入異步操作,從而不影響同步操作的繼續執行,在異步操作完成后,回調返回相應的結果。
小結
APM模型的引入,使得編寫異步程序變的如此簡單,只需定義委托,將要執行的方法包含其中,并調用Begin/End方法對,即可實現異步編程。在一些基礎類庫中,也已經提供了異步操作的方法,直接調用即可。
同時我們可以看到,BeginInvoke方法,實際上是調用了線程池中的線程進行操作,因此APM模型也應屬于多線程程序,同時包含主線程與線程池線程。
但是APM模型也存在一些缺點:
若不使用回調機制,則需等待異步操作完成后才能繼續執行,此時未達到異步操作的效果。
在異步操作的過程中,無法取消,也無法得知操作進度。
若編寫GUI程序,異步操作內容與主線程未在同一線程,操作控件時會引起線程安全問題。
為了解決這些缺陷,微軟推出了其他的異步模式,預知后事如何,且聽下回分解。
您的點贊和在看是我創作的最大動力,感謝支持
公眾號:wacky的碎碎念
知乎:wacky
總結
以上是生活随笔為你收集整理的浅谈.Net异步编程的前世今生----APM篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅谈.Net异步编程的前世今生----E
- 下一篇: 浅谈.Net异步编程的前世今生----T