C#中的线程三(结合ProgressBar学习Control.BeginInvoke)
通过前面2篇的学习应该得出以下结论
1、Delegate.BeginInvoke中执行的方法是异步的
1 public static void Start2()
2 {
3 Console.WriteLine("main thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
4 //DoSomethingDelegate del = new DoSomethingDelegate(Method1);
5 DoSomethingDelegate del = Method1;
6 del.BeginInvoke("this is delegate method", null,null) Console.WriteLine("main thread other things...");
7 }
相当于另开了一个线程来执行Method1方法
2. 如果在UI线程里做Control.BeginInvoke,执行到的方法并没有做到异步
1 private void butBeginInvoke_Click(object sender, EventArgs e) {
2 //A代码段.......
3 this.BeginInvoke(new BeginInvokeDelegate(BeginInvokeMethod));
4 //B代码段......
5 }
也就是说此段代码里,BeginInvokeMethod方法并没有异步执行到,也即没有新开线程做BeginInvokeMethod这个方法
3.如果要让Control.BeginInvoke做到异步,需要在UI线程里新开一个线程,在这个新开的线程里调用Control.BeginInvoke,才能有异步功能
1 private Thread beginInvokeThread;
2 private delegate void beginInvokeDelegate();
3 private void StartMethod(){
4 //C代码段......
5 Control.BeginInvoke(new beginInvokeDelegate(beginInvokeMethod));
6 //D代码段......
7 }
8 private void beginInvokeMethod(){
9 //E代码段
10 }
11 private void butBeginInvoke_Click(object sender, EventArgs e) {
12 //A代码段.......
13 beginInvokeThread = new Thread(new ThreadStart(StartMethod));
14 beginInvokeThread .Start();
15 //B代码段......
16 }
有了上面的理解,我们结合实例来继续学习Control.BeginInvoke
二、ProgressBar的使用
WinForm里有这样一种场景,就是一边处理数据,一边用ProgressBar显示进度,要做出这种功能来如果还是用单线程那种普通的思路,按顺序编码下来,进度条就会从0突然变成100,失去了进度功能。
1.假如我们现在要读取大量数据, 需要用ProgressBar来显示进度,我们先定义一个类,此类中有一个方法Work是用来读取数据的!
1 public class ProgressBarWork
2 {
3 public void Work()
4 {
5 int iTotal = 50;//计算工作量
6 int iCount = 0;
7 for (int i = 0; i < iTotal; i++)
8 {
9 System.Threading.Thread.Sleep(6);//模拟工作 iCount = i;
10 }
11 } }
继续完善此方法,这个方法虽然完成了读取数据的任务,但是如何让外部的ProgressBar知道此方法执行的状态呢?
答案是:该方法从开始执行,中间每次读取,执行完毕要暴露出3个事件来,通知在这三种状态下,ProgressBar应该如何显示,为此我们要声明一个委托,三个事件!
既然要用到事件,还需要用到自定义的EventArgs来传递状态,为此我们定义一个EventArgs
1 public class WorkEventArgs : EventArgs
2 {
3 //主要是用来往外传递信息的
4 public WorkStage Stage;
5 public string Info = "";
6 public int Position = 0;
7 public WorkEventArgs(WorkStage Stage, string Info, int Position)
8 {
9 this.Stage = Stage;
10 this.Info = Info;
11 this.Position = Position;
12 }
13 }
14 public enum WorkStage
15 {
16 BeginWork, //准备工作
17 DoWork, //正在工作
18 EndWork, //工作结束
19 }
接着在ProgressBarWork类中定义事件
1 public delegate void PBWorkEventHandler(object sender, WorkEventArgs e);
2 public class ProgressBarWork
3 {
4 //开始事件
5 public event PBWorkEventHandler OnStartWorkEvent;
6 //执行事件
7 public event PBWorkEventHandler OnDoWorkEvent;
8 //结束事件
9 public event PBWorkEventHandler OnEndWorkEvent;
10
11 public void Work()
12 {
13 int iTotal = 50;//计算工作量
14 int iCount = 0;
15 SendEvents(new WorkEventArgs(WorkStage.BeginWork, "start work", iTotal));
16 for (int i = 0; i < iTotal; i++)
17 {
18 System.Threading.Thread.Sleep(6);//模拟工作
19 iCount = i;
20 SendEvents(new WorkEventArgs(WorkStage.DoWork, "working" + iCount.ToString(), iCount));
21 }
22 SendEvents(new WorkEventArgs(WorkStage.EndWork, "end work", iTotal));
23 }
24
25 private void SendEvents(WorkEventArgs e)
26 {
27 switch (e.Stage)
28 {
29 case WorkStage.BeginWork:
30 if (OnStartWorkEvent != null) OnStartWorkEvent(this, e);
31 break;
32 case WorkStage.DoWork:
33 if (OnDoWorkEvent != null) OnDoWorkEvent(this, e);
34 break;
35 case WorkStage.EndWork:
36 if (OnEndWorkEvent != null) OnEndWorkEvent(this, e);
37 break;
38 default:
39 if (OnDoWorkEvent != null) OnDoWorkEvent(this, e);
40 break;
41
42 }
43 }
这样就在"方法执行前","执行中","执行结束"这三种状态下暴露了三个不同的事件!我们要在这三个不同的事件下,控制ProgressBar的状态
拿"方法执行前"来说,我们需要在UI线程中做如下编码
1 ProgressBarWork work = new ProgressBarWork(); 2 //订阅事件 3 work.OnStartWorkEvent += new PBWorkEventHandler(WorkStart); 4 //其他事件订阅...
然后调用work.Work()方法来开始读取数据,我们通过前面的学习可以得知,如果要做到ProgressBar的显示和数据处理同步,必须单独开一个线程来做数据处理
1 System.Threading.ThreadStart startWork = work.Work; 2 System.Threading.Thread thread = new System.Threading.Thread(startWork); 3 thread.Start();
这样在WorkStart方法中就可以设置ProgressBar中的初始值了
1 void WorkStart(object sender, WorkEventArgs e)
2 {
3 this.statusProgressBar.Maximum = e.Position;
4 this.statusProgressBar.Value = 0;
5 }
如果按照单线程的思想,这样编码是没有问题的,但是如果运行这段程序,会抛出如下异常!

什么原因?这里的this.statusProgressBar是不会运行成功的,因为这个方法不是有UI线程来调用的,必须通过control.Invoke来给ProgressBar赋值!
1 void WorkStart(object sender, WorkEventArgs e)
2 {
3 PBWorkEventHandler del = SetMaxValue;
4 this.BeginInvoke(del, new object[] { sender, e });
5 }
6 private void SetMaxValue(object sender, WorkEventArgs e)
7 {
8 this.statusProgressBar.Maximum = e.Position;
9 this.statusProgressBar.Value = 0;
10 }
这样就确保了SetMaxValue方法是在UI线程中执行的
UI界面完整代码如下:
1 private void ImitateProgressBar()
2 {
3 ProgressBarWork work = new ProgressBarWork();
4 //订阅事件
5 work.OnStartWorkEvent += new PBWorkEventHandler(WorkStart);
6 work.OnDoWorkEvent += new PBWorkEventHandler(Working);
7 work.OnEndWorkEvent += new PBWorkEventHandler(WorkEnd);
8 System.Threading.ThreadStart startWork = work.Work;
9 System.Threading.Thread thread = new System.Threading.Thread(startWork);
10 thread.Start();
11 }
12
13 void WorkStart(object sender, WorkEventArgs e)
14 {
15 PBWorkEventHandler del = SetMaxValue;
16 this.BeginInvoke(del, new object[] { sender, e });
17
18 }
19 private void SetMaxValue(object sender, WorkEventArgs e)
20 {
21 this.statusProgressBar.Maximum = e.Position;
22 this.statusProgressBar.Value = 0;
23 }
24 void Working(object sender, WorkEventArgs e)
25 {
26 PBWorkEventHandler del = SetNowValue;
27 this.BeginInvoke(del, new object[] { sender, e });
28 }
29 private void SetNowValue(object sender, WorkEventArgs e)
30 {
31 this.statusProgressBar.Value = e.Position;
32 }
33
34 void WorkEnd(object sender, WorkEventArgs e)
35 {
36 PBWorkEventHandler del = SetEndValue;
37 this.BeginInvoke(del, new object[] { sender, e });
38 }
39 private void SetEndValue(object sender, WorkEventArgs e)
40 {
41 this.statusProgressBar.Value = e.Position;
42
43 }
其实在以上代码中,我们还可以通过this.Invoke(del, new object[] { sender, e });来调用,效果是跟this.BeginInvoke(del, new object[] { sender, e });调用时一样的,因为
1 void WorkStart(object sender, WorkEventArgs e)
2 {
3 PBWorkEventHandler del = SetMaxValue;
4 this.Invoke(del, new object[] { sender, e });
5
6 }
这个方法已经是在新开的线程上执行的,只要确保在新的线程上利用UI线程去给ProgressBar赋值即可
三、在这个例子中我们还可以看出Control.Invoke和Control.BeginInvoke这两个方法的重要功能:
是在多线程的环境下 确保用UI线程去执行一些调用控件的方法,因为其他线程无法访问UI控件!!!!,这是上篇文章所没有提到的!

浙公网安备 33010602011771号