使用多線程載入多個Xml檔案到TreeView控制項
來源:互聯網
上載者:User
treeview|xml|多線程|載入|控制項 在很多情況下程式員需要採用多線程來開發應用程式,使用者可以在前台操作資料或其他工作,在背景程式正在載入很大的一些檔案,而這一過程不會影響到前台的使用者。在這篇文章中,我來講述一下怎樣通過多個線程來載入多個檔案。在這個例子中我們將來研究這樣一件事情,讀取多個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;//通過這標誌來控制 QueueMonitorThreadprivate 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 { XmlElement element_main = (XmlElement)nList.Item(m);//emp_table nList1 = element_main.ChildNodes ;//Emps for( int k =0;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 { 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; }} 結論:這個例子裡面的設計對於顯示大量的檔案是很有用的。這裡又一個限制就是一旦點擊了終止按紐,監視線程就會終止。要使能夠再次使用需要重其應用程式。希望這篇文章裡面的一些思想對你會有所協助(帶參數調用線程和建立多線程任務)。