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

    作者:Richie Zhang
    声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    读书笔记:A Philosophy of Software Design
    面向对象编程—价值万亿美元的灾难
    刚哥谈架构 (二) 我眼中的架构师
    软件质量成本神话
    API 如何选择 REST,GraphQL还是gRPC
    影响您的代码库的10个编程代码味道
    为什么要不断重构
    php导出excel表格的使用
    浅谈HTTP中Get与Post的区别
    C# 程序配置文件的操作(ConfigurationManager的使用)
  • 原文地址:https://www.cnblogs.com/asdyzh/p/9869955.html
Copyright © 2020-2023  润新知