使用多線程加載多個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;
}
}
結論:這個例子裡面的設計對於顯示大量的文件是很有用的。這裡又一個限制就是一旦點擊了終止按紐,監視線程就會終止。要使能夠再次使用需要重其應用程序。
希望這篇文章裡面的一些思想對你會有所幫助(帶參數調用線程和創建多線程任務)。
|
|