當你進行 C# 程式開發的時候,同一個時間,只會有一個執行緒來幫助您執行程式,例如,當我們想要讀取四個網址的內容的時候,我們會寫類似如下的程式碼;因此,當我們執行方法 SequenceThreads() 的時候,就會依序執行方法內的程式碼,所以,當第一個 GetWebContent 方法執行完成之後,才會繼續執行下一個 GetWebContent 方法,直到四個網站資料都讀取完成。
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Thread Class] 的使用方式
了解更多關於 [HttpClient Class] 的使用方式
在進行抓取網站資料的時候,我們會呼叫方法 GetWebContent,這裡會使用
Thread.CurrentThread.ManagedThreadId
顯示出當時正在執行的受管理的執行緒ID代碼,讓你清楚現在的是使用哪個執行緒來執行;所以,再進行非多工模式的抓取網頁資料的測試程式碼的時候,所看到的受管理的執行緒ID,都是一樣的。
雖然,我們使用
HttpClient
類別的 client.GetStringAsync(url.ToString()).Result
表示式來取得特定網頁上的資料,不過,因為使用了 Result
屬性,所以,這行表示式是採用同步的方式執行的,不是非同步的方式。 static string[] Urls = new string[]
{
"https://www.microsoft.com",
"https://tw.yahoo.com/",
"http://www.msn.com/zh-tw/",
"https://world.taobao.com/"
};
private static void SequenceThreads()
{
Stopwatch sw = new Stopwatch();
sw.Start();
GetWebContent(Urls[0]);
GetWebContent(Urls[1]);
GetWebContent(Urls[2]);
GetWebContent(Urls[3]);
sw.Stop();
Console.WriteLine($"抓取四個網站內容 花費時間為 {sw.Elapsed.TotalMilliseconds} ms");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
}
private static void GetWebContent(object url)
{
Console.WriteLine($"{url} 執行緒 ID {Thread.CurrentThread.ManagedThreadId}");
Stopwatch sw = new Stopwatch();
sw.Start();
using (var client = new HttpClient())
{
string content = client.GetStringAsync(url.ToString()).Result;
Console.WriteLine($"{url} 的內容大小為 {content.Length} bytes");
}
sw.Stop();
Console.WriteLine($"{url} 花費時間為 {sw.Elapsed.TotalMilliseconds} ms");
}
我們從這個測試過程的輸出內容,可以看到全部抓取四個網頁完成所需要的時間,為這四個抓取網頁的時間總和。
底下是執行結果輸出內容
https://www.microsoft.com 執行緒 ID 1
https://www.microsoft.com 的內容大小為 1020 bytes
https://www.microsoft.com 花費時間為 271.7432 ms
https://tw.yahoo.com/ 執行緒 ID 1
https://tw.yahoo.com/ 的內容大小為 194903 bytes
https://tw.yahoo.com/ 花費時間為 141.2671 ms
http://www.msn.com/zh-tw/ 執行緒 ID 1
http://www.msn.com/zh-tw/ 的內容大小為 45733 bytes
http://www.msn.com/zh-tw/ 花費時間為 161.3392 ms
https://world.taobao.com/ 執行緒 ID 1
https://world.taobao.com/ 的內容大小為 220949 bytes
https://world.taobao.com/ 花費時間為 314.1775 ms
抓取四個網站內容 花費時間為 921.3975 ms
Press any key for continuing...
現在,讓我們使用多執行緒的方式來同時抓取這些網頁的資料,在此,我們建立了四個執行緒 Thread 物件,並且在建構函是傳遞了
ParameterizedThreadStart
類別的物件;而這個 ParameterizedThreadStart
型別其實是個委派型別 public delegate void ParameterizedThreadStart(object obj);
,也就是說,我們要建立的執行緒物件,需要一個委派方法,如此,這個執行緒將會多工執行這個方法。
在這裡,我們在要開始進行多執行緒運作的時候,使用 Thread.CurrentThread.ManagedThreadId 屬性,顯示出這個應用程式的主執行緒 ID。
為了要能夠量測出同時抓取四個網頁的總共花費時間,我們使用了執行緒的 Join 方法,這是微軟官方的說明
封鎖呼叫執行緒,直到此執行個體所代表的執行緒終止為止
。 private static void MultiThreads()
{
Thread thread1 = new Thread(GetWebContent);
Thread thread2 = new Thread(GetWebContent);
Thread thread3 = new Thread(GetWebContent);
Thread thread4 = new Thread(GetWebContent);
Console.WriteLine($"主執行緒 ID {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"啟動執行緒");
Stopwatch sw = new Stopwatch();
sw.Start();
thread1.Start(Urls[0]);
thread2.Start(Urls[1]);
thread3.Start(Urls[2]);
thread4.Start(Urls[3]);
thread1.Join();
thread2.Join();
thread3.Join();
thread4.Join();
sw.Stop();
Console.WriteLine($"抓取四個網站內容 花費時間為 {sw.Elapsed.TotalMilliseconds} ms");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
}
從執行結果可以看的出來,同時抓取四個網頁所需要花費的總共時間,大約為抓取某個網頁花費最多的時間,而不是累計總和時間。
底下是執行結果輸出內容
主執行緒 ID 1
啟動執行緒
https://world.taobao.com/ 執行緒 ID 13
https://tw.yahoo.com/ 執行緒 ID 11
http://www.msn.com/zh-tw/ 執行緒 ID 12
https://www.microsoft.com 執行緒 ID 10
https://www.microsoft.com 的內容大小為 1020 bytes
https://www.microsoft.com 花費時間為 47.7937 ms
https://world.taobao.com/ 的內容大小為 220949 bytes
https://world.taobao.com/ 花費時間為 77.1401 ms
http://www.msn.com/zh-tw/ 的內容大小為 45597 bytes
http://www.msn.com/zh-tw/ 花費時間為 126.1487 ms
https://tw.yahoo.com/ 的內容大小為 195623 bytes
https://tw.yahoo.com/ 花費時間為 946.1321 ms
抓取四個網站內容 花費時間為 973.0811 ms
Press any key for continuing...
最後,我們一樣要進行多工的抓取網頁工作,只不過,我們在這裡使用
執行緒的集區 Thread Pool
來處理這樣需求,微軟官方對於 執行緒的集區
的定義為:提供執行緒的集區,可用來執行工作、張貼工作項目、處理非同步 I/O、代表其他執行緒等候,以及處理計時器,ThreadPool 類別為您的應用程式提供了受到系統管理的背景工作執行緒集區,讓您專注於應用程式工作上,而不是執行緒的管理。 如果您有需要在背景處理的簡短工作,Managed 執行緒集區是利用多重執行緒的一個簡單方式。
。
ThreadPool類別的 QueueUserWorkItem 靜態方法,可以接受兩個引數,第一個為委派類型的 WaitCallback (他的委派宣告為
public delegate void WaitCallback(Object state);
),另外一個是要傳入到 WaitCallback 委派函式內的方法引數。執行緒集區執行緒為背景執行緒
private static void MultiThreadPool()
{
ThreadPool.QueueUserWorkItem(GetWebContent, Urls[0]);
ThreadPool.QueueUserWorkItem(GetWebContent, Urls[1]);
ThreadPool.QueueUserWorkItem(GetWebContent, Urls[2]);
ThreadPool.QueueUserWorkItem(GetWebContent, Urls[3]);
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
}
底下是執行結果輸出內容
https://www.microsoft.com 執行緒 ID 16
https://world.taobao.com/ 執行緒 ID 22
http://www.msn.com/zh-tw/ 執行緒 ID 18
https://tw.yahoo.com/ 執行緒 ID 15
https://www.microsoft.com 的內容大小為 1020 bytes
https://www.microsoft.com 花費時間為 151.1066 ms
http://www.msn.com/zh-tw/ 的內容大小為 46023 bytes
http://www.msn.com/zh-tw/ 花費時間為 160.9714 ms
https://world.taobao.com/ 的內容大小為 220949 bytes
https://world.taobao.com/ 花費時間為 225.9449 ms
https://tw.yahoo.com/ 的內容大小為 191718 bytes
https://tw.yahoo.com/ 花費時間為 816.9125 ms
底下是完整的測試程式碼
class Program
{
static string[] Urls = new string[]
{
"https://www.microsoft.com",
"https://tw.yahoo.com/",
"http://www.msn.com/zh-tw/",
"https://world.taobao.com/"
};
static void Main(string[] args)
{
SequenceThreads();
MultiThreads();
MultiThreadPool();
}
private static void MultiThreadPool()
{
ThreadPool.QueueUserWorkItem(GetWebContent, Urls[0]);
ThreadPool.QueueUserWorkItem(GetWebContent, Urls[1]);
ThreadPool.QueueUserWorkItem(GetWebContent, Urls[2]);
ThreadPool.QueueUserWorkItem(GetWebContent, Urls[3]);
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
}
private static void SequenceThreads()
{
Stopwatch sw = new Stopwatch();
sw.Start();
GetWebContent(Urls[0]);
GetWebContent(Urls[1]);
GetWebContent(Urls[2]);
GetWebContent(Urls[3]);
sw.Stop();
Console.WriteLine($"抓取四個網站內容 花費時間為 {sw.Elapsed.TotalMilliseconds} ms");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
}
private static void MultiThreads()
{
Thread thread1 = new Thread(GetWebContent);
Thread thread2 = new Thread(GetWebContent);
Thread thread3 = new Thread(GetWebContent);
Thread thread4 = new Thread(GetWebContent);
Console.WriteLine($"主執行緒 ID {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"啟動執行緒");
Stopwatch sw = new Stopwatch();
sw.Start();
thread1.Start(Urls[0]);
thread2.Start(Urls[1]);
thread3.Start(Urls[2]);
thread4.Start(Urls[3]);
thread1.Join();
thread2.Join();
thread3.Join();
thread4.Join();
sw.Stop();
Console.WriteLine($"抓取四個網站內容 花費時間為 {sw.Elapsed.TotalMilliseconds} ms");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
}
private static void GetWebContent(object url)
{
Console.WriteLine($"{url} 執行緒 ID {Thread.CurrentThread.ManagedThreadId}");
Stopwatch sw = new Stopwatch();
sw.Start();
using (var client = new HttpClient())
{
string content = client.GetStringAsync(url.ToString()).Result;
Console.WriteLine($"{url} 的內容大小為 {content.Length} bytes");
}
sw.Stop();
Console.WriteLine($"{url} 花費時間為 {sw.Elapsed.TotalMilliseconds} ms");
}
}
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式