一、前面的话
对于一些耗时型操作(如文件下载),让主线程去处理不是明智的选择,虽然这样做会使得程序开发起来很简单。因为WinForm程序设计的准则之一就是Responsive,即让用户觉得程序一直在工作,而不是感觉它在(呵呵,事实上,程序不会,只是你没给他表现得机会,如果它有情感,会觉得委屈死)。.Net FrameWork支持在程序用应用线程编程,这可以很好的解决上述问题,不过有时候直接使用Thread和Threadstart显得有些繁琐也没必要,为此.Net Framework提供了BackgroundWorker组件来应付一些简单的应用环境。
本文将分别对上述两种情况的跨线程操作控件方法进行阐述。
二、BackgroundWorker下的跨线程操作控件
BackgroundWorker是个很好的伙计,因为它可以忙你搞定许多脏活累活。具体的讲,它可以自动的帮你创建工作线程,可以在工作时把工作的进展情况告诉给你,可以在工作完成时通知并帮你做一些收尾的工作,当你觉得他很烦的时候,你还可以随时让他停下来。
BackgroundWorker组件提供了三个事件:DoWork,ProgressChanged和RunWorkerCompleted。Dowork顾名思义是用来处理工作业务的,在这里面加入你想让工作线程在后台处理的代码即可。但是在这个事件中不能加入跨线程操作的代码。如下图,当我试图改变Label.Text的值时,抛出了异常信息:
不过这里有个例外,就是对于ToolStrip及其从该类继承过来的容器控件,某些在该容器上的控件(如StatusLabel)可以在工作线程中直接操作。至于为啥,我没有去深究,不过根据图中的提示信息,一个很合理的解释就是这类控件和BackgroundWorker由同一个线程创建。
ProgressChanged和RunWorkerCompleted事件分别用来报告工作线程的工作情况和在工作线程结束后进行一些操作。这两个事件都支持跨线程操作控件。下面通过一个简单的实例进行验证。
用程序实现将一个目录中的文件拷贝至另外一个目录。
1.程序界面设计如下:
2.工作流程:(1)设置源目录和目标目录(2)拷贝文件,在ListView中显示拷贝信息,更新状态栏中的进度条和当前处理文件信息(3)拷贝过程结束后,用MessageBox提示拷贝文件数量,同时清空源目录和目标目录信息。
3.代码实现
1privatevoidbwFileCopy_DoWork(objectsender,DoWorkEventArgse)
2{
3DirectoryInfodi=(DirectoryInfo)e.Argument;
4intiCur=1;
5foreach(FileInfofiindi.GetFiles())
6{
7//为证明ToolTrip对于跨线程的特殊性,在此处更新状态栏的当前处理文件信息
8//实际应用时最好放到ProgressChanged中,通过ReportProgress的参数UserState传递要处理的信息!
9tsslInfo.Text=string.Format("当前正在拷贝文件:{0}",fi.Name);
10
11fi.CopyTo(Path.Combine(targetDir,fi.Name),true);
12bwFileCopy.ReportProgress(GetPercent(iCur,iFileCount),fi.Name);
13iCur++;
14
15}
16e.Result=iCur;
17}
18
19privatevoidbwFileCopy_ProgressChanged(objectsender,ProgressChangedEventArgse)
20{
21//在此处更新状态栏中的进度条
22tssbProcess.Value=e.ProgressPercentage;
23
24//在Listview中添加拷贝信息
25stringFileName=e.UserState.ToString();
26lvOutput.Items.Add(newListViewItem(newstring[]{System.DateTime.Now.ToLongTimeString(),FileName})).EnsureVisible();
27
28}
29privatevoidbwFileCopy_RunWorkerCompleted(objectsender,RunWorkerCompletedEventArgse)
30{
31//清空源目录和目标目录
32tbSource.Text=string.Empty;
33tbTargetDir.Text=string.Empty;
34//提示拷贝文件数量
35MessageBox.Show(string.Format("此过程共拷贝了{0}个文件",e.Result));
36}
37
4.运行结果
三、Thread/ThreadStart下的跨线程操作控件
在一些情况下,Thread/ThreadStart也是有一定市场的,特别在工作线程很多的情况下,显得尤为突出。事实上,在这种环境下要实现上述的例子并不难,代码也没有增加多少,前提是你必须理解Control.Invoke方法。该方法在MSDN上的解释是:在拥有此控件的基础窗口句柄的线程上执行委托。如果你注意到了第一张图片显示的异常信息,你会很快理解这个方法的重大意义。它可以让工作线程的中委托在主线程中执行!因此实现上述例子的思路就是,在工作线程中使用委托来执行操作控件的方法,然后用主窗口的Invoke方法调用!
为了实现BackgroundWorker的ProgressChanged和RunWorkerCompleted事件,定义了ReportProcessInfo委托和DoneAfterCompleted委托。
主要代码如下:
1//实现BackgroundWorker的ProgressChanged事件
2publicdelegatevoidReportProcessInfo(stringInfo,intiPercent);
3/**/////实现BackgroundWorker的RunWorkerCompleted事件
4publicdelegatevoidDoneAfterCompleted(stringInfo);
5
6//更新Listview和ProgressBar的方法
7privatevoidUpdateInfoToUser(stringinfo,intpercent)
8{
9if(InvokeRequired)
10Invoke(newReportProcessInfo(UpdateInfoToUser),info,percent);
11else
12{
13lvOutput.Items.Add(newListViewItem(newstring[]{System.DateTime.Now.ToLongTimeString(),info})).EnsureVisible();
14tssbProcess.Value=percent;
15}
16
17}
18//清空源目录和目标目录信息,显示拷贝文件数的方法
19privatevoidShowUserFilesCountInfo(stringinfo)
20{
21if(InvokeRequired)
22Invoke(newDoneAfterCompleted(ShowUserFilesCountInfo),info);
23else
24{
25tbSource.Text=string.Empty;
26tbTargetDir.Text=string.Empty;
27MessageBox.Show(info);
28}
29
30}
31//线程函数
32privatevoidCopyFiles(objectSourceDir)
33{
34DirectoryInfodi=(DirectoryInfo)SourceDir;
35inticur=0;
36foreach(FileInfofiindi.GetFiles())
37{
38icur++;
39tsslInfo.Text=string.Format("当前正在处理文件:{0}",fi.Name);
40fi.CopyTo(Path.Combine(targetDir,fi.Name),true);
41CopyOneFileIsOK(fi.Name,GetPercent(icur,iFileCount));
42}
43CopyFilesIsCompleted(string.Format("本次操作共拷贝了{0}个文件!",icur));
44}
45
运行结果如下:
四、结尾
代码中的InvokeRequired用于判断该段代码是否是在其他线程中委托调用的,如果为真,就需要在本线程中重新创建一个该委托的实例,并用Invoke方法调用它,让这段代码在本线程中调用。
当代码中需要对多个控件进行操作,最好使用Form的InvokeRequired来判断,并使用Form的Invoke方法调用新建的委托实例。当只对某个控件操作时,就可以只用该控件的InvokeRequired和Invoke。比如tbSource,就可用tbSource.InvokeRequired和tbSource.Invoke。
下面是本文相关代码
原文:http://www.cnblogs.com/kingsky/archive/2009/02/18/1353322.html
相关推荐
本项目使用VS2008平台的C#语言使用多线程操作,并在线程中实现了对主线程控件的使用。
c#在多线程中访问Form中控件的多种解决方案,是我看到的最好最全的方案,贡献给大家看看,希望有所启发
由于多线程可能导致对控件访问的不一致,导致出现问题。C#中默认是要线程安全的,即在访问控件时需要首先判断是否跨线程,如果是跨线程的直接访问,在运行时会抛出异常。 解决办法有两个: 1、不进行线程安全的检查 ...
pdf版
该文档给出了在不同线程间实时更新控件textbox数据的代码,利用了委托(delegate)
该文档给出了c#线程实时更新图表及其他控件数据的完成代码,通过委托delegate和控件的BeginInvoke方法实现数据的绑定
在 Windows 窗体应用程序中,多线程编程是非常重要的,这样可以提高应用程序的响应速度和用户体验。下面我们来讨论如何在 WinForm 中使用 C# 实现多线程编程并更新界面(UI)。 多线程编程 多线程编程是一种使应用...
C#多线程刷新界面
通信时往往会用到多线程,并且存在跨线程调用控件,本文档提供了多种方法解决此类问题
C#多线程控制控件实例,带有详细代码,并有注释说明,简单易懂。
如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。 .NET Framework 有助于在以非...
在C#中,可以使用System.Windows.Forms.Timer控件来实现定时器功能,但是这里介绍的是使用多线程技术来实现定时器。 实现原理: 首先,定义两个类:timer类和interval_date类。timer类用于管理和配置定时器,而...
文档中讲解了C#中在使用多线程,跨线程操作时报错“线程间操作无效: 从不是创建控件“…”的线程访问它”的解决办法。
在C#中,多线程操作控件是一个常见的问题。由于控件是主线程创建的,因此不能直接从另一个线程访问。这篇文章将探讨如何使用threading来实现多线程操作控件。 首先, let's 看一个基本的例子。在这个例子中,我们有...
一个简单的C#多线程和委托更新UI的demo
C#中遇到的多线程问题,该程序为跨线程访问控件的实例
跨线程访问Windows窗体控件,线程带多参数。 跨线程访问Windows窗体控件,线程带多参数。
C# Winform 利用线程延时创建子窗体并且可跨线程控制主窗体的控件
在多线程编程中,经常需要在工作线程中更新界面显示,但是直接调用界面控件的方法是错误的做法。正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过Invoke或BeginInvoke去调用。Invoke和BeginInvoke的...
C# winform 动态创建和 关闭多线程,基于VS2010的完整解决方案。 可运行