C#多線程編程筆記
在開發中經常有遇到因為程序執行的時間過長,而造成程序假死的情況,這是因為我們的程序是同步執行的,當執行到需要長時間的操作時,程序就會等待當前的操作完成,從而造成程序假死。C#的異步與多線程就是為了解決這個問題的。
- 什麽是多線程,舉個簡單的例子,我們在做飯的時候,可以先煮好飯,然後炒菜,然後洗餐具,然後完成,每一個操作都是在前一個操作完成之後才能進行,這就叫做同步執行,我們也可以在邊煮飯的同時炒菜,洗餐具,當所有的工作都做完的時候,飯也就做好了,在這個過程中,煮飯,炒菜是同時進行的,這個就是異步,多線程就類似於主線程在煮飯,然後另開一個新的線程用來炒菜。
- 多線程的優缺點,由上所述,多線程可以在新開的線程中執行操作,所以他不需要等到主線程完成,也不收主線程的影響,自然,也就不會造成程序假死的情況出現了。這樣可以提高用戶的交互體驗,防止用戶以為程序崩潰也不斷重啟。但是由於我們直接感受到的是主線程,而新開的線程其實是在後臺執行的,所以,如果新開的線程出現了異常,我們很難再主線程中捕獲,或者處理,同時新開一個線程也需要計算機硬件的支持,如果線程過多,可能會造成系統變得很卡,資源消費過多的現象。
- 什麽情況下需要使用多線程的技術?根據多線程的特點,其實類似於一個後臺執行的任務,所以一般在以下情況中會使用多線程技術。1、程序執行時間比較長,同時程序的結果不是那麽重要,不應該是主線程等待結果的情況下,可以使用多線程異步執行。例如,登錄之後的驗證過程。2、需要定時刷新的功能,這些功能是定時循環執行,所以可以放在後臺去異步執行,這樣既能保證功能執行了,同時在執行的過程中也不會造成程序卡頓的現象。3、後臺任務,程序只需要執行相關的功能,不需要接收執行結果。例如發送一條短信,我們只需要發送出去即可,不需要知道用戶是否接收到了短信,這樣的情況下可以使用異步發送。其他情況可以參考以上的情況來決定是否需要使用多線程。
多線程編程示例
.Net提供了多種方式實現多線程的編程,包括線程池,Thread,Task等方法,下面對應這些方法給出簡單的示例。
首先,建立一個功能類,此類的作用是多線程需要執行的方法,方法包括,無參數無返回值,有參數無返回值,無參數有返回值,有參數有返回值四種情況。
/// <summary>
/// 此類用於多線程測試的公用方法的定義
/// </summary>
public class CommonClass
{
//註意多線程使用的方法對應的參數類型應該為object
/// <summary>
/// 無返回值有參數的方法,用於無返回值的多線程的類型的使用
/// </summary>
/// <param name="name"></param>
public void ShowName(object name)
{
Console.WriteLine("Your Name Is: {0}", name);
}
/// <summary>
/// 有返回值的方法,用於有返回值的多線程類型的使用
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public string GetName(object name)
{
return name + "English‘s name is Lily";
}
}
上面的代碼只有兩個方法,都帶參數,但是一個有返回值,一個沒有返回值。對於無參數的方法,只需要把傳入的參數設置為null即可。
- 使用ThreadPool實現多線程
ThreadPool顧名思義就是線程池,由於創建一個新的線程的代價比較大,所以如果沒有必要,就不需要創建一個新的線程。線程池就是為此設計的,當新創建一個線程的時候,首先到線程池中查詢是否有空閑線程,如有,則直接使用此線程,這樣就避免了新創建一個線程,若無,則新創建一個線程,並加入到線程池中,當線程執行結束之後,當前線程變為空閑線程,並放入線程池,等待下一個調動。當創建的線程數超過線程池允許的最大線程數之後,線程就需要排隊,等待空閑線程的出現。
/// <summary>
/// 線程池實現多線程的定義
/// 線程池與Thread的區別在於,Thread每次都是新建一個線程,執行完成後就銷毀
/// 而線程池有一個最大線程數,會在池內空閑線程數不夠的時候創建新的線程,並存入
/// 線程池,線程執行完成後並不會銷毀,而是存入線程池,作為空閑線程,等待下一次調用,
/// 超過線程池最大線程數時,不會再創建新的線程,而是排隊等待新的空閑線程,因此,
/// 線程池比Thread的性能更好
/// </summary>
public class ThreadPoolTest
{
//和Thread一樣,線程池只能創建無返回值和最多帶一個參數的多線程方法
public void CreateThread()
{
//ThreadPool.SetMaxThreads(10, 10);//設置線程池最大線程數的方法
//ThreadPool.SetMinThreads(5, 5);//設置線程池最小線程數的方法
var com = new CommonClass();
for (int i = 2; i < 7; i++)
{
//往線程池中添加5個方法,但是線程池中不一定會創建5個線程
ThreadPool.QueueUserWorkItem(new WaitCallback(com.ShowName), i.ToString());
}
}
}
線程池的方法可以有參數,但是不能有返回值,或者只能返回void。
- Thread類實現多線程
Thread用於創建一個線程,他與線程池的區別就在於,他每次都會新創建一個線程,執行完成之後銷毀線程。不存在等待空閑線程的概念,性能取決於硬件設備的性能。
/// <summary>
/// 此類用於多線程的Thread類實現測試
/// </summary>
public class ThreadTest
{
//Thread不能創建帶有返回值的多線程方法,如果需要請使用Task
/// <summary>
/// 用於創建多線程並行的代碼
/// </summary>
public void CreateThread()
{
var com = new CommonClass();
for (int i = 0; i < 5; i++)
{
//ParameterizedThreadStart類型用於定義一個帶參數的方法的線程,如果需要定義不帶參數的多線程實現,請使用ThreadStart
//同時參數只能有一個,如果需要使用多個參數,請使用其他多線程實現方法,或將多個參數直接封裝為一個Class進行傳遞
Thread t = new Thread(new ParameterizedThreadStart(com.ShowName));
t.Start(i.ToString());
}
}
}
上面代碼中紅字部分為要執行的方法,從字面意思即可知道,這個方法是需要定義參數的,如果需要定義無參數的方法,則需要使用new ThreadStart(方法名),t.Start()方法用於開始執行線程,方法的參數即為調用的方法需要傳入的參數。在上面的示例中,傳入的參數即為ShowName方法所需的參數。和ThreadPool一樣,Thread也是不能有返回值的。
- Task實現多線程
Task即為任務,也是實現多線程的一種方式,他定義的方法必須帶一個object的參數,同時可以有返回值。
/// <summary>
/// 通過Task實現多線程的方法
/// Task相比Thread和ThreadPool的區別最直觀的就在於可以實現帶返回值的方法
/// </summary>
public class TaskTest
{
/// <summary>
/// 使用Task創建不帶返回值的多線程
/// 此處的void也可以為Task,在async的異步編程中必須為Task
/// 具體實現請參考CreateReturTaskThread
/// </summary>
public void CreateNoReturnThread()
{
for (int i = 0; i < 5; i++)
{
//此處會形成一個閉包,所以多次返回的結果一樣
Task.Run(() =>
{
Console.WriteLine("This Number is {0}", i);
});
//可以通過如下委托的方式解決上面的問題
//Action<int> act = (a) =>
//{
// Task.Run(() =>
// {
// Console.WriteLine("This Number is {0}", a);
// });
//};
//act(i);
}
}
/// <summary>
/// 返回Task的方法
/// </summary>
/// <returns></returns>
public Task CreateReturTaskThread()
{
return Task.Run(() =>
{
Console.WriteLine("This is a Method for return Task!");
});
}
/// <summary>
/// 創建返回具體值的方法
/// </summary>
/// <returns></returns>
public Task<string> CreateReturnNameThread()
{
return Task.Run(() =>
{
CommonClass com = new CommonClass();
string name = com.GetName("HoS ");
Thread.Sleep(5000);
return name;
});
}
}
在上面的示例中,既有返回void的方法,也有返回具體值的方法,使用Task.Run方法即可定義一個異步執行的方法。Run內部需要傳入一個委托,來定義需要異步執行的功能,相比較Thread,代碼的是想相對復雜,但是可以有返回值,同時創建一個任務相比建創建一個線程的開銷小很多。
- async與await關鍵字
在C#4.0以後為簡化異步操作,添加可async與await兩個關鍵字,這兩個關鍵字的內部也是通過Task來實現異步操作,但是如果不添加這兩個關鍵字,那麽方法就會以同步執行的方式來執行。同時await會等待方法執行的結果,但是在等待的過程中,不會阻塞主線程,也就不會造成程序假死的現象。
/// <summary>
/// 此類用於測試.Net4.0的async實現異步編程的方法
/// </summary>
public class AsyncTest
{
//1.定義一個返回值為Task<T>的方法
public Task<int> GetSum(List<int> list)
{
return Task.Run(() =>
{
return list.Sum();
});
}
//2.定義一個標識了async的方法
/// <summary>
/// 此方法用async標識,代表這是一個異步執行的方法,此方法不會阻塞當前線程
/// </summary>
public async void ShowSum()
{
List<int> list = new List<int>{ 5, 15, 12, 7, 9, 13, 6, 21 };
//此代碼加了await標識,與async配合使用,代表這是一個異步的方法,
//如果不加await方法,則此處代碼會同步執行
//需要註意的是await後面的方法需要等到await執行完成之後才會繼續執行,
//而不是和後面的代碼一起執行,也就是說這裏實現了間接的多線程的同步的功能,
//同時不會卡住主線程,可以解決Winform項目中界面的假死的問題
//因為使用了await關鍵字,所以不需要在使用.Result來獲取Task的結果了
int result = await GetSum(list);
Console.WriteLine("Result is {0}",result);
}
}
如上代碼所示async是需要添加在方法的定義上面,同時微軟建議,所有的async標識的方法返回的都應該是Task<T>如果是返回void則返回Task,上面的方法僅做演示所以未遵照此要求,需要註意。await關鍵字放在需要異步執行的方法之前即可。
以上就是幾種實現多線程的方式。調用的方法也很簡單
//註意:多線程由於是異步執行,所以可能造成無法預知的執行順序,即以下代碼每次執行的結果都可能不同
//1.Thread實現
//ThreadTest.Common.ThreadTest tt = new Common.ThreadTest();
//tt.CreateThread();
//2.ThreadPool實現
//ThreadPoolTest tpt = new ThreadPoolTest();
//tpt.CreateThread();
//3.Task實現
//3.1 無返回值的實現
//TaskTest tt = new TaskTest();
//tt.CreateNoReturnThread();
//3.2 返回一個Task的實現
//var result = tt.CreateReturTaskThread();
//3.3 返回一個Task<T>的實現
//var result = tt.CreateReturnNameThread();
//Console.WriteLine(result.Result);
//4.async的實現
//var at = new AsyncTest();
//at.ShowSum();
//5.多線程的同步實現
//5.1 AutoResetEventTest
//LockTest lt = new LockTest();
//lt.AutoResetEventTest();
//5.2 MutexTest
//lt.MutexTest();
//5.3 MulLock
//lt.MulLock();
//5.3 ManualResetEvent實現線程的手動掛起
//lt.MulLockM();
AsyncTest at = new AsyncTest();
at.ShowSumNo();
Console.ReadKey();
上面的代碼僅供學習使用,如果需要更加深入的了解各種方式,請參考相關的教程,謝謝!
C#多線程編程筆記