1. 程式人生 > >C#多線程編程筆記

C#多線程編程筆記

() 不能 result public lba 設置 return run方法 {0}

在開發中經常有遇到因為程序執行的時間過長,而造成程序假死的情況,這是因為我們的程序是同步執行的,當執行到需要長時間的操作時,程序就會等待當前的操作完成,從而造成程序假死。C#的異步與多線程就是為了解決這個問題的。

  1. 什麽是多線程,舉個簡單的例子,我們在做飯的時候,可以先煮好飯,然後炒菜,然後洗餐具,然後完成,每一個操作都是在前一個操作完成之後才能進行,這就叫做同步執行,我們也可以在邊煮飯的同時炒菜,洗餐具,當所有的工作都做完的時候,飯也就做好了,在這個過程中,煮飯,炒菜是同時進行的,這個就是異步,多線程就類似於主線程在煮飯,然後另開一個新的線程用來炒菜。
  2. 多線程的優缺點,由上所述,多線程可以在新開的線程中執行操作,所以他不需要等到主線程完成,也不收主線程的影響,自然,也就不會造成程序假死的情況出現了。這樣可以提高用戶的交互體驗,防止用戶以為程序崩潰也不斷重啟。但是由於我們直接感受到的是主線程,而新開的線程其實是在後臺執行的,所以,如果新開的線程出現了異常,我們很難再主線程中捕獲,或者處理,同時新開一個線程也需要計算機硬件的支持,如果線程過多,可能會造成系統變得很卡,資源消費過多的現象。
  3. 什麽情況下需要使用多線程的技術?根據多線程的特點,其實類似於一個後臺執行的任務,所以一般在以下情況中會使用多線程技術。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即可。

  1. 使用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。

  1. 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也是不能有返回值的。

  1. 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,代碼的是想相對復雜,但是可以有返回值,同時創建一個任務相比建創建一個線程的開銷小很多。

  1. 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#多線程編程筆記