使用多线程加载多个Xml文件到TreeView控件 翻译:秋枫 在很多情况下程序员需要采用多线程来开发应用程序,用户可以在前台操作数据或其他工作,在后台程序正在加载很大的一些文件,而这一过程不会影响到前台的用户。在这篇文章中,我来讲述一下怎样通过多个线程来加载多个文件。 在这个例子中我们将来研究这样一件事情,读取多个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 { 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; } }
结论:这个例子里面的设计对于显示大量的文件是很有用的。这里又一个限制就是一旦点击了终止按纽,监视线程就会终止。要使能够再次使用需要重其应用程序。 希望这篇文章里面的一些思想对你会有所帮助(带参数调用线程和创建多线程任务)。
|
|
来自: goodwangLib > 《C#》