解决c# progressBar更新出现界面假死
最近一个项目需求中的一个功能是需要用progressBar反映处理文件的进度。
研究了Invoke和BeginInvoke方法。
Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托。
Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托。
我开始的想法是开一个线程处理,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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++) { //这里省略一段执行耗时的操作 <br> 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反映处理耗时的程序的进度。可以开一个子线程处理耗时的部分,然后设置一个全局变量标识耗时的程序处理的进度。
代码段简化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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方法更新。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理