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控件!!!!,这是上篇文章所没有提到的!