多線程與異步編程能夠達到避免調用線程異步阻塞做用,可是二者仍是有點不一樣。算法
多線程與異步編程的異同:數據庫
1.線程是cpu 調度資源和分配的基本單位,本質上是進程中的一段併發執行的代碼。編程
2.線程編程的思惟符合正常人的思惟習慣,線程中的處理程序依然是順序執行,因此編程起來比較方便,可是缺點也是明顯的,多線程的使用會形成多線程之間的上下文切換帶來系統花銷,而且共享變量之間也是會形成死鎖的問題。數組
3.由於異步操做無須額外的線程負擔,而且使用回調的方式進行處理,在設計良好的狀況下,處理函數能夠沒必要使用共享變量(即便沒法徹底不用,最起碼能夠減小 共享變量的數量),減小了死鎖的可能。固然異步操做也並不是完美無暇。編寫異步操做的複雜程度較高,程序主要使用回調方式進行處理,與普通人的思惟方式有些出入,並且難以調試。服務器
在瞭解了線程與異步操做各自的優缺點以後,咱們能夠來探討一下線程和異步的合理用途。我認爲:當須要執行I/O操做時,使用異步操做比使用線程+同步 I/O操做更合適。I/O操做不只包括了直接的文件、網絡的讀寫,還包括數據庫操做、Web Service、HttpRequest以及.net Remoting等跨進程的調用。網絡
而線程的適用範圍則是那種須要長時間CPU運算的場合,例如耗時較長的圖形處理和算法執行。可是每每因爲使用線程編程的簡單和符合習慣,因此不少朋友每每會使用線程來執行耗時較長的I/O操做。這樣在只有少數幾個併發操做的時候還無傷大雅,若是須要處理大量的併發操做時就不合適了。多線程
2、多線程編程併發
恰好這段時間在看網絡編程,在這裏就結合多線程和網絡編程,實現可以應對多客戶端請求的服務端。Socket 類的 Accept() 方法一直等待,直到有客戶端鏈接請求。Socket 網絡編程能夠參考 C# 網絡編程之 Socket 編程 。異步
C# 中有專門的異步網絡編程方法,具體能夠參考 幾種Socket服務器模型比較。async
多線程實現一個併發服務器的例子:
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();
}
}
3、異步編程
基於異步的編程方法有三種:
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(); //一個沒有返回值的方法 });}