[.net 物件導向程式設計進階] (18) 多執行緒(Multithreading)(三) 利用多執行緒提高程式效能(下)

yubinfeng發表於2015-07-28

[.net 物件導向程式設計進階] (18) 多執行緒(Multithreading)(二) 利用多執行緒提高程式效能(下)

本節導讀:

上節說了執行緒同步中使用執行緒鎖和執行緒通知的方式來處理資源共享問題,這些是多執行緒的基本原理。

.NET 4.0以後對多執行緒的實現變得更簡單了。

本節主要討論.NET4.0多執行緒的新特性——使用Task類建立多執行緒。

讀前必備:

A. LINQ使用  [.net 物件導向程式設計基礎] (20) LINQ使用

B. 泛型          [.net 物件導向程式設計基礎] (18) 泛型

1. 執行緒池ThreadPool 

在介紹4.0以後的多執行緒新特徵之前,先簡單說一下執行緒池。

通過前面對多執行緒的學習,我們發現多執行緒的建立和使用並不難,難的在於多執行緒的管理,特別是執行緒數量級很多的情況下,如何進行管理和資源釋放。需要使用執行緒池來解決。

簡單來說執行緒池就是.NET提供的存放執行緒的一個物件容器。

執行緒池執行緒分為兩類:工作執行緒和IO執行緒.
執行緒池是一種多執行緒處理形式,處理過程中將任務新增到佇列,然後在建立執行緒後自動啟動這些任務。

對於執行緒池,可使用要執行的過程的委託呼叫 System.Threading.ThreadPool.QueueUserWorkItem(System.Threading.WaitCallback) 方法

下面是一個執行緒池的示例:

先設定一個建立執行緒總數靜態欄位:

 static readonly int totalThreads = 20;

使用執行緒池建立執行緒:

//設定最小執行緒和最大執行緒數
ThreadPool.SetMinThreads(2, 2);
ThreadPool.SetMaxThreads(20, 20);

for (int i = 0; i < totalThreads; i++)
{
    ThreadPool.QueueUserWorkItem(o =>
    {
        Thread.Sleep(1000);
        int a, b;
        ThreadPool.GetAvailableThreads(out a, out b);
        Console.WriteLine(string.Format("({0}/{1}) #{2} : {3}", a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
    });
}
Console.WriteLine("主執行緒完成");

執行結果如下:

 

2. Task

ThreadPoolQueueUserWorkItem()方法發起一次非同步的執行緒執行很簡單,但是該方法最大的問題是沒有一個內建的機制讓你知道操作什麼時候完成,有沒有一個內建的機制在操作完成後獲得一個返回值。為此,在.NET 4.0 以後,我們可以使用System.Threading.Tasks中的Task類。這也是.NET 4.0以後多執行緒的推薦做法。

構造一個Task<T>物件,併為泛型T引數傳遞一個操作的返回型別。

Task類可以使用多種方法建立多執行緒,下面詳細介紹。 

2.1 使用Factory屬性 

Task 例項可以用各種不同的方式建立。 最常見的方法是使用任務的 Factory 屬性檢索可用來建立用於多個用途的TaskFactory 例項。

 例如,要建立執行操作的 Task,可以使用工廠的 StartNew 方法:          

//最簡單的執行緒示例
Task.Factory.StartNew(() =>
{
    Console.WriteLine("我是使用Factory屬性建立的執行緒");
});

執行結果如下:

如果想簡單的建立一個Task,那麼使用Factory.NewStart()來建立,很簡便。

如果像對所建立的Task附加更多的定製和設定特定的屬性,請繼續往下看。

2.2 使用Task例項實現多執行緒 

//簡單的Task例項建立執行緒
Action<object> action = (object obj) =>
{
    Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
};
Task t1 = new Task(action, "引數");
t1.Start();

 執行結果如下:

 

//簡寫上面例項,並建立100個執行緒
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
int m = 100;
Task[] tasks = new Task[m];
for (int i = 0; i < m; i++)
{
    tasks[i] = new Task((object obj) =>
        {
            Thread.Sleep(200);
            Console.WriteLine("Task={0}, obj={1}, Thread={2},當前時間:{3}",
            Task.CurrentId, obj.ToString(),
            Thread.CurrentThread.ManagedThreadId,
            System.DateTime.Now.ToString());
        }, "引數" + i.ToString()
    );
    tasks[i].Start();
}
           
Task.WaitAll(tasks);
Console.WriteLine("執行緒耗時:{0},當前時間:{1}" ,watch.ElapsedMilliseconds,System.DateTime.Now.ToString());

執行結果如下:

 2.3 Task傳入引數

上面介紹了使用一個Action委託來完成任程,那麼給執行緒中傳入引數,就可以使用System.Action<object>來完成。

 傳入一個引數的示例:

/// <summary>
/// 一個引數的方法
/// </summary>
/// <param name="parameter"></param>
static void MyMethod(string parameter)
{
    Console.WriteLine("{0}", parameter);
}

呼叫如下:

//Task傳入一個引數
Task myTask = new Task((parameter) => MyMethod(parameter.ToString()), "aaa");
myTask.Start();

執行結果如下:

 傳入多個引數如下:

/// <summary>
/// 多個引數的方法
/// </summary>
/// <param name="parameter1"></param>
/// <param name="parameter2"></param>
/// <param name="parameter3"></param>
static void MyMethod(string parameter1,int parameter2,DateTime parameter3)
{
    Console.WriteLine("{0} {1} {2}", parameter1,parameter2.ToString(),parameter3.ToString());
}

呼叫如下:

//Task傳入多個引數
for (int i = 1; i <= 20; i++)
{              
    new Task(() => { MyMethod("我的執行緒", i, DateTime.Now); }).Start();
    Thread.Sleep(200);
}

執行結果如下:

 

 對於傳入多個引數,可以使用無引數委託包裝一個多引數的方法來完成。 

2.4 Task的結果

要獲取Task的結果,在建立Task的時候,就要採用Task<T>來例項化一個Task。

其中的T就是Task執行完成之後返回結果的型別。

通過Task例項的Result屬性就可以獲取結果。

System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
Task<int> myTask = new Task<int>(() =>
{
    int sum = 0;
    for (int i = 0; i < 10000; i++)
        sum += i;
    return sum;
});
myTask.Start();           
Console.WriteLine("結果: {0} 耗時:{1}", myTask.Result,watch.ElapsedMilliseconds);

執行結果如下:

使用Factory屬性來完成上面的示例:

//使用Factory屬性建立
System.Diagnostics.Stopwatch watchSecond = System.Diagnostics.Stopwatch.StartNew();
Task<int> myTaskSecond = Task.Factory.StartNew<int>(() =>
{
    int sum = 0;
    for (int i = 0; i < 10000; i++)
        sum += i;
    return sum;
});            
Console.WriteLine("結果: {0} 耗時:{1}", myTaskSecond.Result, watchSecond.ElapsedMilliseconds);

執行結果如下:

多執行緒除以上的一些基礎知識,在處理各種並行任務和多核程式設計中的使用,小夥伴可以參考專門關於多執行緒的書籍學習。

想要完全深入的學習多執行緒需要慢慢修煉,不斷積累。 

3. 本節要點:

A.本點簡單介紹了執行緒池ThreadPool的使用;

B.介紹一使用Task進行多執行緒建立及Tast的引數傳入和返回結果。

==============================================================================================

返回目錄

<如果對你有幫助,記得點一下推薦哦,如有有不明白或錯誤之處,請多交流>

<對本系列文章閱讀有困難的朋友,請先看《.net 物件導向程式設計基礎》>

<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>

.NET 技術交流群:467189533 .NET 程式設計

==============================================================================================

相關文章