使用多線程加載多個Xml文件到TreeView控件 - 中國WEB開發者網絡 (http://www.webasp.net) -- 技術教程 (http://www.webasp.net/article/) --- 使用多線程加載多個Xml文件到TreeView控件 (http://www.webasp.net/article/12/11674.htm) |
| -- 作者:未知 -- 發佈日期: 2004-07-06 |
| 使用多線程加載多個Xml文件到TreeView控件
原代碼:LoadingXmlInTvMTCode.zip 在很多情況下程序員需要採用多線程來開發應用程序,用戶可以在前台操作數據或其他工作,在後台程序正在加載很大的一些文件,而這一過程不會影響到前台的用戶。在這篇文章中,我來講述一下怎樣通過多個線程來加載多個文件。 在這個例子中我們將來研究這樣一件事情,讀取多個Xml文件並通過TreeView把它們顯示出來。我們可以通過數據庫來完成,不過為了保持例子的簡單這裡採用了Xml文件。 你會注意到我們有兩個xml文件同原代碼放在一起。 程序用戶界面如下: Filedisplayer類用來顯示上面的窗體。窗體的包括一些按紐:瀏覽按紐,運行按紐,終止按紐以及退出按紐。應用程序可以通過點擊退出按紐來結束整個程序的運行。當點擊瀏覽的時候會打開一個文件選擇對話框來加載Xml文件。當然你也可以直接在文本框中輸入文件全路徑。 private void selectbutton_click(object sender, System.EventArgs e) { OpenFileDialog openFileDialog1 = new OpenFileDialog(); openFileDialog1.Filter = "All Files (*.*)|*.*|Text Files (*.txt)|*.txt"; if (openFileDialog1.ShowDialog () == DialogResult.OK) { String fileName = openFileDialog1.FileName; //如果文件擴展名為xml,選擇成功 if ( (fileName.Length != 0) && (fileName.EndsWith("xml"))) { filename_box.Text=fileName; } } } 一旦選擇了一個文件,用戶可以通過點擊運行來讀取文件數據。顯示的結果就如上面TreeView中所看到的。這篇文章的主要目的就是給讀者一個方法來執行多線程。終止按紐用來退出執行的任務。 現在我們已經習慣的各種用戶界面控件,那就讓我們來研究其他部分。你可以在代碼中注意到我們已經編寫了下面一些代碼(如下): private Thread QueueMonitorThread ;//定義一個線程,用來監視隊列 private RequestQueue req_queue;//放了加載的文件信息(文件名) private bool m_bAbort;//通過這標誌來控制 QueueMonitorThread private ThreadEventDelegate onTreeViewElement;//異步代理調用,切換線程來更新TreeView 在RequestQueue.CS文件中我們定義了一個RequestQueue類,他是一個隊列用來存儲文件名數據。在這個例子中設置了隊列的容量為5。因此隊列最多能放五個文件名在裡面。在Add方法中有一個邏輯,如果添加文件成功返回1,如果失敗(隊列為滿)返回0;Remove方法用來移動隊列頭索引,如果頭索引等與尾索引那隊列就為空了;getFile方法是用來獲取隊列最前面的項。如果為空返回0, setSize方法用來重新設置隊列的容量,如果調用原來的數據就會被扔掉(這裡作者是用數組來模擬環形隊列,設置容量會重新實例化一個數組);isEmpty方法用來判斷隊列是否有空。 備註:你也可以使用System.Collections命名空間下的Queue類。 當點擊了運行按紐後,程序會從獲取路徑文本框中獲取文件名並把它加入到FileInfo結構(很奇怪這裡作者的結構只是存了一個string)。最後把這個結構加入到了隊列。QueueMonitorThread線程會半秒鐘去掃瞄一次隊列。 private void processbutton_click(object sender, System.EventArgs e) { FileInfo f = new FileInfo(); f.fName=this.filename_box.Text; //如果隊列已滿那就等待隊列有空時再添加FilInfo while ( req_queue.isEmpty()!=1) { if( req_queue.isEmpty() == 1 ) break; Thread.Sleep(200); } req_queue.add(f); } 下面是繼承窗體的構造函數。 public filedisplayer() { InitializeComponent(); req_queue = new RequestQueue(); //設置隊列容量為5 req_queue.setSize(5); //默認監視線程沒有終止 m_bAbort = false; //實例會監視線程 QueueMonitorThread = new Thread( new ThreadStart(QueueMonitorfunc)); QueueMonitorThread.Start(); //代理更新TreeView,BeginInvoke onTreeViewElement = new ThreadEventDelegate(populateTreeView); } 下面是線程的執行方法。 public void QueueMonitorfunc() { while( true) { if(isAbort())//判斷線程是否跳出循環,結束線程 { break; } Object o = req_queue.getFile();//從隊列獲取文件 if( (o is FileInfo ))//隊列是否為空 { FileInfo f = (FileInfo)req_queue.getFile(); string filename = f.fName; parse(f);//啟動一個線程處理 req_queue.remove();//從隊列中移出文件 } Thread.Sleep(500); } } 請注意上面的QueueMonitorThread線程,他自己不處理文件。只是檢測隊列,如果有文件存在就調用parse方法,而parse方法為每個文件處理生成一個線程。 方法內容如下: private void parse(FileInfo info) { //返回一個線程 Thread t = parserThread.CreateThread (new parserThread.Start(parserMethod), info); t.Start (); //阻塞調用線程 t.Join (Timeout.Infinite); } 下面是創建線程的類: public class parserThread { //代理代參數的方法 public delegate void Start (object obj); //這個類用來解決ThreadStart只能代理無參數無返回值函數而設計。 private class Argument { public object obj1;//用來保存文件名數據 public Start s1; //建立該方法是為了由ThreadStart來代理, public void parse() { s1(obj1); } } //創建返回線程。 public static Thread CreateThread (Start s, Object arg1) { Argument arg = new Argument(); arg.obj1 = arg1; arg.s1 = s; Thread t = new Thread (new ThreadStart (arg.parse)); return t; } } 下面是parserMethod方法: public void parserMethod(object obj) { FileInfo fInfo = (FileInfo)obj; process_xml((fInfo.fName)); } 如果你看了ParserThread類的CreateThread方法,那上面的parserMethod方法就很清楚了。我們成功的完成了參數的傳遞。下面是process_xml方法: public void process_xml(String name) { try { XmlDocument doc = new XmlDocument(); String fname = name; doc.Load(fname); XmlNodeList nList1; XmlNodeList nList2; XmlNodeList nList; nList=doc.GetElementsByTagName("EmpDataSet"); for( int m =0;m<nList.Count;m++) { XmlElement element_main = (XmlElement)nList.Item(m);//emp_table nList1 = element_main.ChildNodes ;//Emps for( int k =0;k<nList1.Count;k++) { XmlElement element_fchild = (XmlElement)nList1.Item(k); nList2 = element_fchild.ChildNodes ; IEmpDetails emp = new EmpDetails(); if( m_bAbort) { return; } for( int j =0;j<nList2.Count;j++) { XmlElement child_element = (XmlElement)nList2.Item(j); if( child_element.Name == "Emp_id" ) { emp.empId = System.Convert.ToInt32(child_element.InnerText); } if( child_element.Name == "Emp_Name" ) { emp.empName = child_element.InnerText; } if( child_element.Name == "Emp_Address" ) { emp.empAddress = child_element.InnerText; } if( child_element.Name == "Emp_City" ) { emp.empCity = child_element.InnerText; } if( child_element.Name == "Emp_State" ) { emp.empState = child_element.InnerText; } if( child_element.Name == "Emp_Pin" ) { emp.empPin = child_element.InnerText; } if( child_element.Name == "Emp_Country" ) { emp.empCountry = child_element.InnerText; } else if( child_element.Name == "Emp_Email" ) { emp.empEmail = child_element.InnerText; } } //切換線程到TreeView所被創建的線程,從而更新TreeView,不過這裡是異步的。 BeginInvoke(onTreeViewElement, new object[] {this, new ThreadEventArgs(emp)}); } } } catch(Exception exp) { MessageBox.Show("Error...in displaying treeview "+exp.Message); } } EmpDetails類實現了IEmpDetails接口,用來包含數據,略。 BeginInvoke方法異步執行,裡面通過代理onTreeViewElement來調用populateTreeView方法: private void populateTreeView(object sender, ThreadEventArgs e) { IEmpDetails ex = e.empDetails; TreeNode n = new TreeNode("EMP :"+ex.empId); n.Nodes.Add(ex.empName); n.Nodes.Add(ex.empAddress); n.Nodes.Add(ex.empCity); n.Nodes.Add(ex.empState); n.Nodes.Add(ex.empPin); n.Nodes.Add(ex.empCountry); n.Nodes.Add(ex.empEmail); treeView1.Nodes.Add(n); } 另外一個就是參數類,用來傳輸xml文件的內容: public class ThreadEventArgs : EventArgs { IEmpDetails _empDetails; public IEmpDetails empDetails { get{ return _empDetails;} } public ThreadEventArgs(IEmpDetails empDetails) { this._empDetails = empDetails; } } 結論:這個例子裡面的設計對於顯示大量的文件是很有用的。這裡又一個限制就是一旦點擊了終止按紐,監視線程就會終止。要使能夠再次使用需要重其應用程序。 希望這篇文章裡面的一些思想對你會有所幫助(帶參數調用線程和創建多線程任務)。 |
| webasp.net |