本文主要是介绍WinForm中UI假死的解决方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、前言
WinForm
中的UI
假死其实是个老生常谈的问题了,但最近还是很多同志问该如何解决,今天就来说明一下。测试程序界面如下图所示:一个Button
和一个ProgressBar
。
2、主线程的阻塞和UI更新问题
先看一个简单的例子:
using System;
using System.Threading;
using System.Windows.Forms;namespace App
{public partial class MainForm : Form{public MainForm(){InitializeComponent();}private void btn_Click(object sender, EventArgs e){progressBar1.Minimum = 0;progressBar1.Maximum = 100;progressBar1.Style = ProgressBarStyle.Blocks;progressBar1.Step = 1;progressBar1.Value = 0;int max = progressBar1.Maximum;for (int i = 1; i <= max; i++){progressBar1.Value = i;Thread.Sleep(100);}}}
}
运行上面的代码,你会发现在程序运行期间界面无法拖动,UI
处于假死状态。原因其实很简单:循环中的Thread.Sleep(100)
阻塞了线程。既然主线程被阻塞了,那我们自然会想到创建一个子线程去更新控件,现在修改一下代码:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace App
{public partial class MainForm : Form{public MainForm(){InitializeComponent();}private void btn_Click(object sender, EventArgs e){progressBar1.Minimum = 0;progressBar1.Maximum = 100;progressBar1.Style = ProgressBarStyle.Blocks;progressBar1.Step = 1;progressBar1.Value = 0;Task task = Task.Run(() =>{int max = progressBar1.Maximum;for (int i = 1; i <= max; i++){progressBar1.Value = i;Thread.Sleep(100);}});}}
}
运行上面的代码,发现报错:线程间操作无效: 从不是创建控件“progressBar1”的线程访问它
。
3、使用Delagate解决界面卡死问题
首先明确一点:UI
线程位于主线程,如果想要在子线程里更新UI
状态,则必须要将其切换到主线程,然后进行更新操作。UI
控件一般会提供Invoke、InvokeRequired
,其中InvokeRequired
用于判断是否有子线程在更新UI
控件,如果有则返回true
,Invoke
用于将控制权切换到UI
线程,代码如下:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace App
{public delegate void UpdateProgressBarDelegate(int num);public partial class MainForm : Form{public MainForm(){InitializeComponent();}private void btn_Click(object sender, EventArgs e){progressBar1.Minimum = 0;progressBar1.Maximum = 100;progressBar1.Style = ProgressBarStyle.Blocks;progressBar1.Step = 1;progressBar1.Value = 0;Task task = Task.Run(() =>{UpdateProgressBar();});}private void UpdateProgressBar(){int max = progressBar1.Maximum;for (int i = 1; i <= max; i++){UpdateProgressBarCallback(i);Thread.Sleep(100);}}private void UpdateProgressBarCallback(int num){if (progressBar1.InvokeRequired){progressBar1.Invoke(new UpdateProgressBarDelegate(UpdateProgressBarCallback), new object[] { num });}else{progressBar1.Value = num;}}}
}
4、使用BackgroundWorker解决界面卡死问题
WinForm
中的BackgroundWorker
组件也可以很好地解决界面卡死问题,如下图所示:
BackgroundWorker
组件的核心就是DoWork
、ProgressChanged
、ProgressChanged
三个事件,代码如下:
using System;
using System.Threading;
using System.Windows.Forms;namespace App
{public partial class MainForm : Form{public MainForm(){InitializeComponent();backgroundWorker1.WorkerReportsProgress = true;backgroundWorker1.WorkerSupportsCancellation = true;}private void btn_Click(object sender, EventArgs e){if (backgroundWorker1.IsBusy){return;}progressBar1.Minimum = 0;progressBar1.Maximum = 100;progressBar1.Style = ProgressBarStyle.Blocks;progressBar1.Step = 1;progressBar1.Value = 0;backgroundWorker1.RunWorkerAsync();}private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e){int max = progressBar1.Maximum;for (int i = 1; i <= max; i++){backgroundWorker1.ReportProgress(i);Thread.Sleep(100);}}private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e){progressBar1.Value = e.ProgressPercentage;}private void backgroundWorker1_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e){MessageBox.Show("完成");}}
}
5、使用async、await解决界面卡死问题
上面提到的Delegate
和BackgroundWorker
组件主要是针对UI
的更新问题,其实还有一种情况也会造成UI
的卡死,那就是某些耗时操作。先来看一段代码:
using System;
using System.Threading;
using System.Windows.Forms;namespace App
{public partial class MainForm : Form{public MainForm(){InitializeComponent();}private void btn_Click(object sender, EventArgs e){string message = GetMessage();MessageBox.Show(message);}private string GetMessage(){Thread.Sleep(5000);return "Hello World";}}
}
在上面的代码中,GetMessage
方法模拟了一个耗时操作,运行程序,发现UI
假死,界面无法拖动,此时就可以考虑使用async
和await
来解决。代码如下:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace App
{public partial class MainForm : Form{public MainForm(){InitializeComponent();}private async void btn_Click(object sender, EventArgs e){string message = await GetMessageAsync();MessageBox.Show(message);}private async Task<string> GetMessageAsync(){return await Task.Run(() =>{Thread.Sleep(5000);return "Hello World";});}}
}
6、使用Task解决界面卡死问题
对于一些老项目,比如.NET 4.0
版本下的代码,开发者无法使用async
、await
,此时就可以考虑使用Task
来实现。代码如下:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace App
{public partial class MainForm : Form{public MainForm(){InitializeComponent();}private void btn_Click(object sender, EventArgs e){string message = "";Task task = Task.Factory.StartNew(() =>{message = GetMessage();}).ContinueWith(t =>{MessageBox.Show(message);});}private string GetMessage(){Thread.Sleep(5000);return "Hello World";}}
}
7、结语
本文主要介绍了WinForm
中的UI
假死解决方案。在实际开发中,合理使用多线程可以有效提高界面的人机交互体验。
这篇关于WinForm中UI假死的解决方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!