使用多線程加載多個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