使用多线程加载多个 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
{
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;
}
}
结论:这个例子里面的设计对于显示大量的文件是很有用的。这里又一个限制就是一旦点击了终止按纽,监视线程就会终止。要使能够再次使用需要重其应用程序。
希望这篇文章里面的一些思想对你会有所帮助(带参数调用线程和创建多线程任务)。
发表于 @ 2004年06月10日 10:36:00 | 评论( 17 <script type="text/javascript"> AddFeedbackCountStack("22037") </script> ) | 举报 | 收藏
旧一篇:在DataGrid中使用下拉列表框和设置焦点[翻译] | 新一篇:用C#去除程序源代码的SourceSafe管理
<script type="text/javascript"> var CurrentEntryId = '22037'; var CurrentUserName = 'zhzuo'; </script> <script src="http://hi.images.csdn.net/js/blog/feedback.js?v=2009060902" type="text/javascript"></script>-
张博 发表于2005年1月7日 18:08:00
举报
-
秋枫你好!
看了你翻译的
使用多线程加载多个Xml文件到TreeView控件
http://blog.csdn.net/zhzuo/archive/2004/06/10/22037.aspx
这个例程.也下了源码实际测试了一下效果,真的很好用.
可以一下子加载很多xml文件,treeview的节点也在一直添加,并且UI对各种鼠标事件的反应还是很灵敏,比如移动,最大化等.
这就是我要的效果.
分析一下这个例程.
我的理解是:(不对请扔转头啊^_^)
先是一个主线程.(main thread)
在窗口的构造函数中,启动了一个监视线程QueueMonitorThread
"他自己不处理文件。只是检测队列,如果有文件存在就调用parse方法,而parse方法为每个文件处理生成一个线程。"
称为parserThread
该线程主要做两个工作,1是从xml文件中读取信息,2是调用BeginInvoke在UI界面中添加节点.
每添加一个节点都会调用一下BeginInvoke.
按照秋枫的意思,其实BeginInvoke.还是在UI线程即主线程执行.
不知道上面理解是否正确.???
那么下面我想对这个例程简化一下.其实我想实现的也是一个线程一边给treeview 添加节点,另外UI线程还能及时响应用户操作.
我省去了监视线程.
直接是点击按钮新开了一个线程.然后在ppp函数(类似例程中process_xml函数)中,循环添加节点.也是每次循环都调用了 BeginInvoke来添加节点,可是这样却会"几乎死掉",UI线程几乎无法响应鼠标操作.不知道怎么不对.和上面的例程有什么不一样么?
????
private void button1_Click(object sender, System.EventArgs e)
{
System.Threading.Thread tr=new Thread(new System.Threading.ThreadStart(ppp));
tr.Start();
tr.Join();
}
private void ppp()
{
for(int i=0;i<10000;i++)
{
this.treeView1.BeginInvoke(new dele(addNode));
}
}
private delegate void dele();
private int j=0;
private void addNode()
{
this.treeView1.Nodes.Add(j.ToString());
this.treeView1.Refresh();
//Thread.Sleep(100);
j++;
}
另外我也用过Application.DoEvents(),这样基本可以做到一边加载,一边响应UI,不过这样始终会