与 UI 无关的处理应该单独、异步执行,然后当执行完毕之后才调用主线程来更新界面。例如测试下面的例子:
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
void button1_Click(object sender, EventArgs e)
{
button1.Text = "测试开始";
Test();
}
void Test()
{
Thread.Sleep(10000);
this.button1.Text = "测试完成";
}
}
}
当你点击按钮的时候,你会发现10秒钟之内界面卡死了,窗口根本用鼠标拖动不了!这就是很糟糕的程序。而改成
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
async void button1_Click(object sender, EventArgs e)
{
button1.Text = "测试开始";
await Test();
this.button1.Text = "测试完成";
}
Task Test()
{
return Task.Run(() => //把与UI刷新无关的语句放到这个匿名委托中执行
{
Thread.Sleep(10000);
});
}
}
}
你会发现在耗时过程执行过程中也能自由地拖动窗口。 假设在异步并发过程中要修改窗口控件,那么需要使用委托来访问控件。例如:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
async void button1_Click(object sender, EventArgs e)
{
button1.Text = "测试开始";
await Test();
this.button1.Text = "测试完成";
}
Task Test()
{
return Task.Run(() => //把与UI刷新无关的语句放到这个匿名委托中执行
{
Thread.Sleep(2500);
this.button1.BeginInvoke((Action)delegate
{
this.button1.Text = "1";
});
Thread.Sleep(2500);
this.button1.BeginInvoke((Action)delegate
{
this.button1.Text = "2";
});
Thread.Sleep(2500);
this.button1.BeginInvoke((Action)delegate
{
this.button1.Text = "3";
});
Thread.Sleep(2500);
});
}
}
}
这里,在耗时的异步过程执行过程中访问了 UI 界面控件,就需要使用 BeginInvoke 委托来操作控件,使用 BeginInvoke.Invoke 委托来操作控件,这是15年前就在 .net 中有的操作规范。
PS:
推荐的使用方法
了解到 ThreadPoolTaskScheduler 的默认行为之后,我们可以做这些事情来充分利用线程池带来的优势:
- 对于 IO 操作,尽量使用原生提供的 Async 方法,这些方法使用的是 IO 完成端口,占用线程池中的 IO 线程而不是普通线程(不要自己使用 Task.Run 占用线程池资源);
- 对于没有 Async 版本的 IO 操作,如果可能耗时很长,则指定 CreateOptions 为 LongRunning(这样便会直接开一个新线程,而不是使用线程池)。
- 其他短时间执行的任务才推荐使用 Task.Run。