在用Winform打开窗口,或是执行某些操作时,总会有一些操作非常耗时。而在执行这些方法时,界面却是处于卡死状态的,这对于用户来说是非常不好的体验。本文就介绍,如何将一些与UI无关的耗时操作放在后台执行,而不影响当前用户的界面。
1.同步执行操作
同步操作即我们正常的操作,下面将代码给出。新建一个Winform应用程序,应该会有如下文件:
在Form1.Designer.cs中改成以下代码:
namespace WinFormsApp1
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
btnCounter = new Button();
lbl_Progress = new Label();
SuspendLayout();
//
// btnCounter
//
btnCounter.Location = new Point(337, 86);
btnCounter.Name = "btnCounter";
btnCounter.Size = new Size(126, 45);
btnCounter.TabIndex = 0;
btnCounter.Text = "0";
btnCounter.UseVisualStyleBackColor = true;
btnCounter.Click += btnCounter_Click;
//
// lbl_Progress
//
lbl_Progress.Font = new Font("Microsoft YaHei UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
lbl_Progress.Location = new Point(300, 185);
lbl_Progress.Name = "lbl_Progress";
lbl_Progress.Size = new Size(200, 25);
lbl_Progress.TabIndex = 1;
lbl_Progress.Text = "开始执行";
lbl_Progress.TextAlign = ContentAlignment.MiddleCenter;
//
// Form1
//
AutoScaleDimensions = new SizeF(9F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
Controls.Add(lbl_Progress);
Controls.Add(btnCounter);
Name = "Form1";
Text = "Form1";
ResumeLayout(false);
}
#endregion
private Button btnCounter;
private Label lbl_Progress;
}
}
这些代码是写界面的,为了简单,界面上只有两个控件:
在Form1.cs中改成以下代码:
using System;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
namespace WinFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnCounter_Click(object sender, EventArgs e)
{
//第一次点击按钮时,执行方法
if (btnCounter.Text == "0")
{
//做一些耗时操作
DoSomething();
}
//点击按钮后累加计数
btnCounter.Text = (int.Parse(btnCounter.Text) + 1).ToString();
}
/// <summary>
/// 做一些耗时操作
/// </summary>
private void DoSomething()
{
//1.等待1秒
Thread.Sleep(1000);
//显示进度
lbl_Progress.Text = "1.等待1秒 已完成";
//2.耗时操作
Thread.Sleep(100 * Random.Shared.Next(5, 20));
//显示进度
lbl_Progress.Text = "2.耗时操作 已完成";
//3.等待1秒
Thread.Sleep(1000);
//显示进度
lbl_Progress.Text = "3.等待1秒 已完成";
}
}
}
运行程序后,点击一下按钮,执行耗时操作。我们会发现,界面被完全卡死,用户体验非常不好。下面我们使用异步执行耗时操作。
2.异步执行操作
将Form1.cs中,引入命令空间:
using System.Threading.Tasks;
将DoSomething函数改成如下代码:
/// <summary>
/// 做一些耗时操作
/// </summary>
private void DoSomething()
{
//异步执行操作
Task.Factory.StartNew(() =>
{
//1.等待1秒
Thread.Sleep(1000);
this.BeginInvoke((MethodInvoker)delegate ()
{
//显示进度
lbl_Progress.Text = "1.等待1秒 已完成";
});
//2.耗时操作
Thread.Sleep(100 * Random.Shared.Next(5, 20));
this.BeginInvoke((MethodInvoker)delegate ()
{
//显示进度
lbl_Progress.Text = "2.耗时操作 已完成";
});
//3.等待1秒
Thread.Sleep(1000);
this.BeginInvoke((MethodInvoker)delegate ()
{
//显示进度
lbl_Progress.Text = "3.等待1秒 已完成";
});
});
}
再次执行程序,发现,程序一边执行耗时操作,一边将耗时操作的过程显示到UI中。这样用户体验就很好了。
3.代码解析
Task.Factory.StartNew(() => {});这句话,是开始一个新的线程执行操作,这样就不会卡死UI了。而在这个线程中操作UI是不行的,因为UI只能存在在一个线程中,即UI线程(也是主线程)。这时如果要进行UI显示,则需要用到this.BeginInvoke((MethodInvoker)delegate (){});这个函数将一段代码传递给UI线程,当UI线程进入到下一次消息循环时,就会找个时间,将这段代码执行了,通常一次消息循环非常快(0.1秒都不到)。这时,UI线程可以正常地显示UI了,同时UI线程还可以执行一些其他的非耗时操作,用户体验感很好。