編程不能死記硬背,要靠多實踐操作
如今的網絡越來越發達,分享一個文件是如此的簡單。特別是有了電驢、迅雷這樣的下載軟件就更加如虎添翼了,想從網上下載一個幾個G大小的文件,真是不費吹灰之力。好,廢話太多了,直接進入咱們今天的主題吧。
要實現像迅雷一樣的多線程下載,核心問題是要將多線程的概念以及怎么實現的問題弄清。
當然,本文技術含量很低,大牛請直接繞道。
多線程是相對單線程來說的,具體可以參考百度百科里的解釋:http://baike.baidu.com/view/65706.htm
每個程序運行都有一個最基本的主線程,用於處理界面繪畫,人機交互,后台處理等過程,因此如果是在單線程程序里操作打量耗時的動作,主界面就會很卡,甚至是無法工作。因此不管您是不是喜歡,最好都別用主線程把一切事務包攬,否則很難給用戶一個舒爽的客戶體驗。
那么在C#里如何實現多線程呢?
下面讓我們實現一個最簡單的多線程實例;
為了演示方便,我們新建一個winform項目,取名為 MultiThreadDemo。
先創建一個足夠讓你的程序卡住不動的方法函數:
private void Display() { while (true) textBox1.Text = new Random().NextDouble().ToString(); }
然后給button1添加調用,發現確實夠卡吧,誰讓你把那種死循環的事情交給主線程去做呢,一個人又畫圖,又要算數,哪還有時間給你答復。
using System.Threading;
接着補充一下button1里面的代碼,給他創建一個線程,我們把這線程取名叫“UiThread”用於專門處理顯示吧。

private void button1_Click(object sender, EventArgs e) { Thread thread = new Thread(Display);//創建一個線程 thread.Start(); // Display(); }
如果你急着運行,肯定會回過頭來罵我了,怎么不行呢,是不是什么會提示:“線程間操作無效: 從不是創建控件“textBox1”的線程訪問它。”。因為主線程和你創建的那個線程是兩個互不相干的線程,兩個陌生人怎么打交道?也就是當你這個UiThread沒經過主線程同意就去調用textBox1,別人會讓你那么做嗎?
因此,為了處理他倆工作不協調的問題,特意強制性取消線程警告.在構造函數里添加一句:
public Form1() { InitializeComponent(); Control.CheckForIllegalCrossThreadCalls = false;//加上這句就不會警告了 }
這樣一個簡單的多線程程序就誕生了。不過有個時候有很多代碼需要用到委托,又不想單獨創建一個函數,就可以這樣做:

private void button1_Click(object sender, EventArgs e) { ThreadStart threadStart = new ThreadStart(delegate { Display(); });//創建一個委托,這樣可以調用任意參數的函數了,甚至是零星的代碼都可以 Thread thread = new Thread(threadStart); thread.Start(); }
不過並不推薦這么做,這在線程上是不安全的,有很大的概率會使程序奔潰。
通過上面的練習,我們知道創建一個線程可以多做一些事,同樣,我們多創建幾個線程,做的事豈不是更多?這是必須的。
接下來正式走進我們今天的正題:多線程采集
要想多線程采集,首先要解決單個下載。
using System.Net; using System.IO;

/// <summary> /// 轉載請加上本人博客鏈接 /// </summary> /// <param name="richtextBox"></param> /// <param name="i"></param> static void Request(RichTextBox richtextBox,int i) { richtextBox.AppendText(string.Format("線程{0}開始接收\n", Thread.CurrentThread.Name)); ServicePointManager.DefaultConnectionLimit = 1000; HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(string.Format("http://news.cnblogs.com/n/{0}/", (int)i));//這里的i最嗨是158100到158999,符合博客園url規則才能采集到 StreamWriter sw = File.CreateText(string.Format(Environment.CurrentDirectory + "\\{0}.htm", i)); try { HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); Stream stream = httpWebResponse.GetResponseStream(); StreamReader sr = new StreamReader(stream); string html = sr.ReadToEnd(); richtextBox.AppendText(string.Format(Thread.CurrentThread.Name + "接收完畢")); sw.Write(html); sw.Close(); } catch { richtextBox.AppendText(string.Format("線程{0}不存在此地址,跳過\n", Thread.CurrentThread.Name)); sw.Write(string.Format("線程{0}不存在此地址,跳過\n", Thread.CurrentThread.Name)); return; } }
然后在在button2里調用

private void button2_Click(object sender, EventArgs e) { ThreadStart threadStart = new ThreadStart(delegate { Request(richTextBox1, 158100); });//創建一個委托,這樣可以調用任意參數的函數了,甚至是零星的代碼都可以 Thread thread = new Thread(threadStart); thread.Start(); }
這樣以來單次采集就完成了。
要想像火車頭一樣采集,自然以目前的水平是做不到的。起碼也要把批量采集做出來。無外乎使用多線程。

/// <summary> /// 轉載請加上本人博客鏈接 /// </summary> /// <param name="richtextBox"></param> /// <param name="i"></param> static void Request(RichTextBox richtextBox,int i) { richtextBox.AppendText(string.Format("線程{0}開始接收\n", Thread.CurrentThread.Name)); ServicePointManager.DefaultConnectionLimit = 1000; HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create(string.Format("http://news.cnblogs.com/n/{0}/", (int)i));//這里的i最嗨是158100到158999,符合博客園url規則才能采集到 try { HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); Stream stream = httpWebResponse.GetResponseStream(); StreamReader sr = new StreamReader(stream); string html = sr.ReadToEnd(); richtextBox.AppendText(string.Format(Thread.CurrentThread.Name + "接收完畢\n")); StreamWriter sw = File.CreateText(string.Format(Environment.CurrentDirectory + "\\{0}.htm", i)); sw.Write(html); sw.Close(); } catch { richtextBox.AppendText(string.Format("線程{0}不存在此地址,跳過\n", Thread.CurrentThread.Name)); } } private void button2_Click(object sender, EventArgs e) { Thread.CurrentThread.Name = "主線程"; Thread[] threads = new Thread[51]; DateTime endTime = DateTime.Now; DateTime startTime = DateTime.Now; TimeSpan timeSpan = endTime - startTime; string span = timeSpan.TotalSeconds.ToString(); startTime = DateTime.Now; Mutex mt = new Mutex(); mt.WaitOne(); for (int i = 158300; i >158250; i--) { threads[158300 - i] = new Thread(new ParameterizedThreadStart(delegate { Request(richTextBox1, i); })); threads[158300 - i].Name = "線程" + (i).ToString(); ; threads[158300 - i].Start(); } mt.ReleaseMutex(); endTime = DateTime.Now; timeSpan = endTime - startTime; span = timeSpan.TotalSeconds.ToString(); richTextBox1.AppendText(string.Format("多線程接受的話共花費了{0}秒鍾\n", span)); }
多線程采集就完成了。其實本文講來講去主要是圍繞創建線程這一話題,技術含量相當低,就當給剛入門的朋友練練手吧!
教程每天都會更新,歡迎繼續關注。