前言
WinForm中的UI假死其实是个老生常谈的问题了,但最近还是很多人问我该如何解决,所以今天就来说明一下如何解决UI假死的问题。
实验程序界面如下图所示:
正文
方法一 async + await + Task
首先看下面一段代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 开始
private void btnStart_Click(object sender, EventArgs e)
{
string message = GetMessage();
MessageBox.Show(message);
}
// 一个耗时任务
private string GetMessage()
{
Thread.Sleep(10000);
return "Hello World";
}
}
}
在上面的代码中,GetMessage()方法耗时10秒钟,如果你点击按钮,那么在10秒钟内窗体将处于假死状态。
这种情况很常见,之所以会造成UI假死的原因也很简单:某个函数耗时太久。
在遇见这种情况的时候,我们就可以考虑使用async + await + Task来解决,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 开始
private async void btnStart_Click(object sender, EventArgs e)
{
string message = await GetMessage();
MessageBox.Show(message);
}
// 一个耗时任务
private async Task<string> GetMessage()
{
return await Task<string>.Run(() =>
{
Thread.Sleep(10000);
return "Hello World";
});
}
}
}
运行之后点击按钮,你会发现UI没有假死,窗体可以随意拖动了。
方法二:使用BackgroundWorker组件
在很多时候,我们需要动态显示当前的程序执行进度,以便让用户了解程序已经执行到哪一步了。
很多同志都会这么写:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 开始
private void btnStart_Click(object sender, EventArgs e)
{
int max = pgbStatus.Maximum;
for (int i = 1; i <= max; i++)
{
pgbStatus.Value++;
Thread.Sleep(1000);
}
}
}
}
功能确实是实现了,进度条能够显示当前执行的进度,可惜UI还是处于假死状态,所以用户体验还是不好。
其实WinForm已经给我们提供了一个处理多线程任务的组件BackgroundWorker,使用它可以轻松让你的程序告别UI假死,如下图所示:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.bgw.WorkerReportsProgress = true;
this.bgw.WorkerSupportsCancellation = true;
this.bgw.DoWork += DoWork;
this.bgw.ProgressChanged += ProgressChanged;
this.bgw.RunWorkerCompleted += RunWorkerCompleted;
}
// 开始
private void btnStart_Click(object sender, EventArgs e)
{
if (bgw.IsBusy)
{
return;
}
bgw.RunWorkerAsync();
}
// DoWork
private void DoWork(object sender, DoWorkEventArgs e)
{
int max = pgbStatus.Maximum;
for (int i = 1; i <= max; i++)
{
bgw.ReportProgress(i);
Thread.Sleep(1000);
}
}
// ProgressChanged
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbStatus.Value = e.ProgressPercentage;
}
// RunWorkerCompleted
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("完成");
}
}
}
运行程序点击按钮,你会发现UI没有处于假死状态,窗体可以随意拖动。首先需要明确一点:UI线程位于主线程,如果想要在子线程里更新UI状态,必须要将其切换到主线程,最后进行更新操作。UI控件一般会提供Invoke、InvokeRequired,其中InvokeRequired用于判断是否有子线程在更新UI控件,如果有则返回true,Invoke用于将控制权切换到UI线程,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 开始
private void btnStart_Click(object sender, EventArgs e)
{
Task task = Task.Run(() =>
{
int max = pgbStatus.Maximum;
for (int i = 1; i <= max; i++)
{
UpdateValue(i);
Thread.Sleep(1000);
}
});
}
// 处理线程
private void UpdateValue(int num)
{
if (pgbStatus.InvokeRequired)
{
pgbStatus.Invoke(new Action<int>(UpdateValue), new object[] { num });
}
else
{
pgbStatus.Value = num;
}
}
}
}
方法三也可以解决UI的假死问题,当然也不一定要用Task,利用Thread也可以实现一样的效果。通过上述方法,可以有效地解决 WinForms 应用程序中 UI 假死的问题。选择哪种方法取决于具体的场景和需求。BackgroundWorker 和 Task+ async/await 是最常用和推荐的方法,因为它们简单易用且功能强大。ThreadPool和手动创建线程则适用于需要更高灵活性的场景。无论选择哪种方法,都要注意在更新 UI 时使用 Invoke或 BeginInvoke,以确保线程安全。如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号[DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!作者:码农家园
出处:codenong.com/cs106719464/声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!
该文章在 2024/12/28 11:58:25 编辑过