winform中的多线程与异步加载
1. 使用async和await关键字
首先,在你的方法前加上async关键字,然后在调用长时间运行的方法前使用await关键字。例如,如果你有一个长时间运行的方法LoadDataAsync(),你可以这样调用它:
public async void LoadDataButton_Click(object sender, EventArgs e) { await Task.Run(() => LoadData()); UpdateUI(); } private void LoadData() { // 模拟长时间运行的任务 Thread.Sleep(5000); // 例如,加载数据可能需要一些时间 } private void UpdateUI() { // 更新UI元素,例如设置Label的文本 this.statusLabel.Text = "数据加载完成"; }
2. 使用BackgroundWorker类
BackgroundWorker类提供了一个简单的方式来执行长时间运行的任务,而不会冻结UI线程。你可以在后台工作项完成后更新UI。详细参考,BackgroundWorker它通过事件驱动的方式,允许在后台线程中执行耗时操作,并通过事件回调更新UI,从而避免
直接从非UI线程更新UI可能引起的线程安全问题。不需要begininvoke。
private void LoadDataButton_Click(object sender, EventArgs e) { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += LoadData; worker.RunWorkerCompleted += UpdateUI; worker.RunWorkerAsync();
//worker.RunWorkerAsync(object argument)//可传参数重置 } private void LoadData(object sender, DoWorkEventArgs e) { // 模拟长时间运行的任务 Thread.Sleep(5000); // 例如,加载数据可能需要一些时间 } private void UpdateUI(object sender, RunWorkerCompletedEventArgs e) { this.statusLabel.Text = "数据加载完成";//不用使用begininvoke吗? }
精简写法:
private void LoadDataAsync()
{
// 创建BackgroundWorker实例
BackgroundWorker worker = new BackgroundWorker();
// 设置后台任务的执行函数
worker.DoWork += (sender, e) =>
{
// 在这里执行数据加载操作
// ...
};
//进度条更新
worker.ProgressChanged += (sender, e) => {
// 在这里更新 UI,显示进度条等。使用 e.ProgressPercentage 获取进度值。
Console.WriteLine($"Progress: {e.ProgressPercentage}%");
};
// 设置后台任务完成后的回调函数
worker.RunWorkerCompleted += (sender, e) =>
{
// 在这里处理数据加载完成后的逻辑
// ...
};
// 启动后台任务
worker.RunWorkerAsync();
}
注意:使用thread类也可以实现。
BeginInvoke方法用于异步地将一个委托封装的操作发送到UI线程的消息队列中。这样,即使是在后台线程中,也可以安全地更新UI控件。BeginInvoke方法不会阻塞当前线程,而是将任务排队,待UI线程空闲时执行。
private void button1_Click(object sender, EventArgs e) { Thread thread = new Thread(new ThreadStart(StartSomeWork)); thread.IsBackground = true; thread.Start(); } private void StartSomeWork() { if (this.InvokeRequired) { BeginInvoke(new EventHandler(RunsOnWorkerThread), null); } else { RunsOnWorkerThread(this, null); } } private void RunsOnWorkerThread(object sender, EventArgs e) { Thread.Sleep(2000); label1.Text = System.DateTime.Now.ToString(); }
3. 使用Task和Task.Run结合Control.Invoke或Control.BeginInvoke
如果你需要在异步操作中更新UI,可以使用Invoke或BeginInvoke方法确保UI更新在UI线程上执行。
private async void LoadDataButton_Click(object sender, EventArgs e) { await Task.Run(() => LoadData()); this.Invoke((MethodInvoker)delegate { UpdateUI(); }); } private void LoadData() { // 模拟长时间运行的任务 Thread.Sleep(5000); // 例如,加载数据可能需要一些时间 } private void UpdateUI() { this.statusLabel.Text = "数据加载完成"; }
4. 使用async和await结合Invoke或BeginInvoke(推荐用于UI更新)
如果你希望在异步方法中直接更新UI,可以在调用UI相关代码前使用Invoke或BeginInvoke。
private async void LoadDataButton_Click(object sender, EventArgs e) { await Task.Run(() => LoadData()); // 后台加载数据 this.Invoke((MethodInvoker)delegate { this.statusLabel.Text = "数据加载完成"; }); // UI线程更新UI }
5、异步http
同步 异步http
其他
BeginInvoke是Invoke的异步版本(Invoke是同步完成的),不过大家不要和上面的System.Windows.Forms.MethodInvoker委托中的BeginInvoke混淆,两者都是利用不同线程来完成工作,但是控件的BeginInvoke方法总是使用UI线程,而其他的异步委托调用方法则是利用
线程池里的线程。相对Invoke而言,使用BeginInvoke稍稍麻烦一点,但还是那句话,异步比同步效果好,尽管复杂些。比如同步方法可能出现这样一种死锁情况:工作者线程通过Invoke同步调用UI线程里的方法时会阻塞,而万一UI线程正在等待工作者线程做某件事时怎么办?因此,
能够使用异步方法时应尽量使用异步方法。
总结:
选择哪种方法取决于你的具体需求和偏好。对于简单的异步操作和UI更新,使用async和await结合Invoke通常是简单且有效的。对于更复杂的后台任务处理,使用BackgroundWorker可能更合适。而Task.Run结合适当的线程同步机制(如Invoke)提供了灵活
的控制方式。
行者常至,为者常成!

浙公网安备 33010602011771号