前言
在.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獲取結果,那麼會發生什麼呢?
如下圖所示: