• 解决c# progressBar更新出现界面假死


    最近一个项目需求中的一个功能是需要用progressBar反映处理文件的进度。

    研究了Invoke和BeginInvoke方法。

    Control.Invoke 方法 (Delegate) :拥有此控件的基础窗口句柄的线程上执行指定的委托。

    Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托。

    我开始的想法是开一个线程处理,代码如下:

             private delegate void DoDataDelegate(object number);
            private void button2_Click(object sender, EventArgs e)
            {
               // Thread myThread = new Thread(DoData);
                Thread myThread = new Thread(new ParameterizedThreadStart(DoData));
                myThread.IsBackground = true;
                myThread.Start(int.Parse(textBox1.Text)); //线程开始  
                Console.WriteLine("主线程继续执行---------------");
            }
            private void DoData(object number)
            {
                if (progressBar1.InvokeRequired)
                {
                    Console.WriteLine("开始");
                    DoDataDelegate d = DoData;
                    progressBar1.BeginInvoke(d, number);
                    //代码段D
                }
                else
                {
                    progressBar1.Maximum = (int)number;
                    Console.WriteLine("准备进行循环调用");
                    for (int i = 0; i < (int)number; i++)
                    {
                      //这里是一段耗时长的代码
                      progressBar1.Value = i + 1;
                    }
                   
                }
            }                
    

     在上述代码中当执行到progressBar1.BeginInvoke(d, number);时,myThread封送消息给UI,然后自己继续执行代码段D,代码段D的执行相对于myThread是异步的。如果将

    progressBar1.BeginInvoke(d, number);换成progressBar1.Invoke(d, number);其余的代码不变,那么代码段D将会等到线程myThread结束后才会执行。无论是调用BeginInvoke或者Invoke,因为调用者是progressBar1,所以更新progressBar1.Value时,是在"拥有此控件的基础窗口句柄的线程(UI线程)执行指定的委托",在更新value值之前有一段耗时长的代码,所以此时UI线程会阻塞,处于假死状态。

    我的解决办法是使用BackgroundWorker 类。

                backgroundWorker1.WorkerReportsProgress = true;
                backgroundWorker1.DoWork += DoWork_Handler;
                backgroundWorker1.ProgressChanged += ProgressChanged_Handler;
             private void button2_Click(object sender, EventArgs e)
            {
                backgroundWorker1.RunWorkerAsync();
            }    
             private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
            {   
                 for (int i = 1; i <= 100; i++)
                {
                   //这里省略一段执行耗时的操作 
    backgroundWorker1.ReportProgress(i); } } private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { // Change the value of the ProgressBar to the BackgroundWorker progress. progressBar1.Value = e.ProgressPercentage; }

     BackgroundWorker.RunWorkerAsync是创建了一个线程处理耗时的操作,backgroundWorker1_DoWork方法执行,执行到backgroundWorker1.ReportProgress(i);会触发backgroundWorker1_ProgressChanged方法的执行,在backgroundWorker1_ProgressChanged方法中,更新value的值。这样做UI界面和线程处理好使的工作是异步的,所以不会造成UI界面卡死的现象。

    我了解了Invoke 和BeginInvoke的原理,但不知道怎么用他们实现更新ProgressBar的值,并保证UI界面不出现假死。所以我选择了BackgroundWorker解决了我的问题。

    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    再次更新。

    经过和老师的讨论,我自己进行了实践。如果用ProgressBar反映处理耗时的程序的进度。可以开一个子线程处理耗时的部分,然后设置一个全局变量标识耗时的程序处理的进度。

    代码段简化如下:

    int  num=0;
    int number=16;
    private void button2_Click(object sender, EventArgs e)
            {
                progressBar1.Maximum = number;
               // Thread myThread = new Thread(DoData);
                Thread myThread = new Thread(new ParameterizedThreadStart(DoData));
                myThread.IsBackground = true;
                myThread.Start(number); //线程开始  
     
            }
            private void DoData(object number)
            {
                    Console.WriteLine("准备进行循环调用"); 
                    for (int i = 0; i < (int)number; i++)
                    {
                      //这里是一段耗时长的代码
    
                        num = i + 1;
                    }
                   
                }
            }          
    

     然后加入一个Timer每隔一定的时间访问num的值。在Timer_tick函数中更新progressBar的value的值。

    补充:在C#中的子线程中不能直接更新UI中的控件值。可以通过

    1.Invoke或者BeginInvoke的方式调用委托的方法

    2.使用BackGroundWorker类取代Thread类进行异步操作

    3.通过设置窗体属性,取消线程安全检查来“避免跨线程操作异常”。(非线程安全,不建议使用)

    4.通过UI线程的SynchronizationContext的Post/Send方法更新。

  • 相关阅读:
    第二章 信息的表示和处理(下)
    第二章 信息的表示和处理
    IDEA中新建子模块
    手动实现一个可重入锁
    Lock接口的认识和使用
    JDK提供的原子类原理与使用
    深入理解volatile原理与使用
    模拟死锁
    模拟自旋锁
    grep 如何自动标注颜色
  • 原文地址:https://www.cnblogs.com/liquan/p/8859206.html
Copyright © 2020-2023  润新知