多線程與異步編程可以達到避免調用線程異步阻塞作用,但是兩者還是有點不同。
多線程與異步編程的異同:
1.線程是cpu 調度資源和分配的基本單位,本質上是進程中的一段並發執行的代碼。
2.線程編程的思維符合正常人的思維習慣,線程中的處理程序依然是順序執行,所以編程起來比較方便,但是缺點也是明顯的,多線程的使用會造成多線程之間的上下文切換帶來系統花銷,並且共享變量之間也是會造成死鎖的問題。
3.因為異步操作無須額外的線程負擔,並且使用回調的方式進行處理,在設計良好的情況下,處理函數可以不必使用共享變量(即使無法完全不用,最起碼可以減少 共享變量的數量),減少了死鎖的可能。當然異步操作也並非完美無暇。編寫異步操作的復雜程度較高,程序主要使用回調方式進行處理,與普通人的思維方式有些出入,而且難以調試。
適用范圍
在了解了線程與異步操作各自的優缺點之后,我們可以來探討一下線程和異步的合理用途。我認為:當需要執行I/O操作時,使用異步操作比使用線程+同步 I/O操作更合適。I/O操作不僅包括了直接的文件、網絡的讀寫,還包括數據庫操作、Web Service、HttpRequest以及.net Remoting等跨進程的調用。
而線程的適用范圍則是那種需要長時間CPU運算的場合,例如耗時較長的圖形處理和算法執行。但是往往由於使用線程編程的簡單和符合習慣,所以很多朋友往往會使用線程來執行耗時較長的I/O操作。這樣在只有少數幾個並發操作的時候還無傷大雅,如果需要處理大量的並發操作時就不合適了。
二、多線程編程
剛好這段時間在看網絡編程,在這里就結合多線程和網絡編程,實現能夠應對多客戶端請求的服務端。Socket 類的 Accept() 方法一直等待,直到有客戶端連接請求。Socket 網絡編程可以參考 C# 網絡編程之 Socket 編程 。
C# 中有專門的異步網絡編程方法,具體可以參考 幾種Socket服務器模型比較。
多線程實現一個並發服務器的例子:
static Socket client;
static void Main(string[] args)
{
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9060); //創建 IPEndPoint 對象,表示接受任何端口 9050 的客戶機地址
Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // TCP
server.Bind(ipep); //綁定
server.Listen(20); //監聽
Console.WriteLine("正在監聽...");
//下面這段代碼阻塞,可以用新線程執行,但可能會出問題
while (true)
{
client = server.Accept(); //收到客戶端請求
//開新線程發送數據
Thread recvthread = new Thread(sendData);
recvthread.IsBackground = true; //后台線程
// 啟動消息服務線程
recvthread.Start();
///也可以開其他線程,如接收數據線程
}
}
static private void sendData()
{
if (client != null)
{
Console.WriteLine("客戶機" + client.RemoteEndPoint + "連接到服務器");
string data = "hello client";
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data); //將 string 轉化為 byte 數組
client.Send(msg); //向客戶端發生數據
Console.WriteLine("發生數據:" + data);
client.Close();
}
}
三、異步編程
基於異步的編程方法有三種:
- 異步編程模式(APM),其中異步操作要求 Begin 和 End 方法(例如,異步寫操作的 BeginWrite 和 EndWrite)。對於新的開發工作不再建議采用此模式。
- 基於事件的異步模式 (EAP) 需要一個具有 Async 后綴的方法,還需要一個或多個事件、事件處理程序、委托類型和 EventArg 派生的類型,在 .NET Framework 2.0 版中引入的。對於新的開發工作不再建議采用此模式。
- 基於任務的異步模式 (TAP),該模式使用一個方法表示異步操作的啟動和完成。.NET Framework 4 中引入了 TAP,並且是 .NET Framework 中異步編程的建議方法。
Task 異步,有以下三種方法創建 Task:
-
Task.Factory.StartNew,比較常用。
-
Task.Run,是.net 4.5中增加的。
-
Task.FromResult,如果結果是已計算,就可以使用這種方法來創建任務。
下面就以 Task.Factory.StartNew 進行異步編程實現一個並發服務器的例子:
static Socket client;
static void Main(string[] args)
{
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9060); //創建 IPEndPoint 對象,表示接受任何端口 9050 的客戶機地址
Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // TCP
server.Bind(ipep); //綁定
server.Listen(20); //監聽
Console.WriteLine("正在監聽...");
//下面這段代碼阻塞,可以放到子線程執行,但是可能會出現問題,可以看最后面分析
while (true)
{
client = server.Accept();
//創建並啟動 task
Task.Factory.StartNew(() =>
{
sendData(); //一個沒有返回值的方法
});
}
}
static private void sendData()
{
if (client != null)
{
Console.WriteLine("客戶機" + client.RemoteEndPoint + "連接到服務器");
string data = "hello client";
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data); //將 string 轉化為 byte 數組
client.Send(msg); //向客戶端發生數據
Console.WriteLine("發生數據:" + data);
client.Close();
}
}
使用 Async 與 Await 進行異步編程
static Socket client;
static Socket server;
static void Main(string[] args)
{
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9060); //創建 IPEndPoint 對象,表示接受任何端口 9050 的客戶機地址
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // TCP
server.Bind(ipep); //綁定
server.Listen(20); //監聽
Console.WriteLine("正在監聽...");
accept();
}
static async void accept()
{
await acceptAsync();
}
static async Task acceptAsync() //異步接受請求
{
while (true)
{
client = server.Accept(); //收到客戶端請求
await sendData();
}
}
static async Task sendData() //異步發生數據
{
if (client != null)
{
Console.WriteLine("客戶機" + client.RemoteEndPoint + "連接到服務器");
string data = "hello client";
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data); //將 string 轉化為 byte 數組
client.Send(msg); //向客戶端發生數據
//添加一個異步方法
await Task.Delay(1000);
Console.WriteLine("發生數據:" + data);
client.Close();
}
}
由於 Main() 函數不能設置為 async 模式,所以增加了一個accept 函數,使用 await 來執行異步操作 acceptAsync() ,等待接受客戶端的請求。同時在異步操作 acceptAsync() 中執行異步 sendData() ,異步發送數據。
一個問題:在上面的三個程序中,采用多線程和Task.Factory.StartNew 實現服務端的兩個程序,如果把下面兩段代碼作為子線程或者異步函數執行,本來是阻塞的函數 client = server.Accept(),卻沒有等待客戶端連接,直接執行過去了??是因為 accept()在同時在靜態函數和多線程中的關系????(因為在非靜態函數中這樣並沒有問題),但是使用Async 與 Await 執行的異步函數卻能正常執行。
while (true)
{
client = server.Accept(); //收到客戶端請求
//開新線程發送數據
Thread recvthread = new Thread(sendData);
recvthread.IsBackground = true; //后台線程
// 啟動消息服務線程
recvthread.Start();
///也可以開其他線程,如接收數據線程
}
while (true)
{
client = server.Accept();
//創建並啟動 task
Task.Factory.StartNew(() =>
{
sendData(); //一個沒有返回值的方法
});
}
