众所周知,使用线程可以极大的提高应用程序的效率和响应性,提高用户体验,但是不可以无节制的使用线程,为什么呢?
线程的开销实际上是非常大的,我们从空间开销和时间开销上分别讨论。
线程的空间开销来自这四个部分:
OutOfMemoryException
。线程的时间开销来自这三个过程:
Spinlock
,并确定下一个要执行的线程,然后释放 Spinlock
。如果下一个线程不在同一个进程内,则需要进行虚拟地址交换。所以,由于要进行如此多的工作,所以创建和销毁一个线程就意味着代价“昂贵”,即使现在的CPU多核多线程,如无节制的使用线程,依旧会严重影响性能。
为了避免程序员无节制地使用线程,微软开发了“线程池”技术。简单来说,线程池就是替开发人员管理工作线程。当一项工作完毕时,CLR不会销毁这个线程,而是会保留这个线程一段时间,看是否有别的工作需要这个线程。至于何时销毁或新起线程,由CLR根据自身的算法来做这个决定。
线程池技术能让我们重点关注业务的实现,而不是线程的性能测试。
微软除实现了线程池外,还需要关注一个类型:BackgroundWorker
。BackgroundWorker
是在内部使用了线程池的技术:同时,在WinForm或WPF编码中,它还给工作线程和UI线程提供了交互的能力。
实际上, Thread
和 ThreadPool
默认都没有提供这种交互能力,而 BackgroundWorker
却通过事件提供了这种能力。这种能力包括:报告进度、支持完成回调、取消任务、暂停任务等。
BackgroundWorker
的简单示例如下:
private BackgroundWorker backgroundWorker = new BackgroundWorker();
private void AsyncButton_Click(object sender, RoutedEventArgs e)
{
//注册要执行的任务
backgroundWorker.DoWork += BackgroundWorker_DoWork;
//注册报告进度
backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
//注册完成时的回调
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
//设置允许任务取消
backgroundWorker.WorkerSupportsCancellation = true;
//设置允许报告进度
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.RunWorkerAsync();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
//取消任务
if (backgroundWorker.IsBusy)
backgroundWorker.CancelAsync();
}
private void BackgroundWorker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
//完成时回调
MessageBox.Show("BackgroundWorker RunWorkerCompleted");
}
private void BackgroundWorker_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
//报告进度
this.textbox.Text = e.ProgressPercentage.ToString();
}
private void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)
{
BackgroundWorker? worker = sender as BackgroundWorker;
if (worker != null)
{
for (int i = 0; i < 20; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
worker.ReportProgress(i);
Thread.Sleep(100);
}
}
}
建议使用WinForm和WPF的开发人员使用 BackgroundWorker
。
ThreadPool
相对于 Thread
来说具有很多优势,但是 ThreadPool
在使用上却存在一定的不方便。比如:
ThreadPool
不支持线程的取消、完成、失败通知等交互性操作。ThreadPool
不支持线程执行的先后次序。所以随着 Task
类及其所提供的异步编程模型的引入,Task
相较ThreadPool
具有更多的优势。大概有一下几点:
ContinueWith()
、 When()
、WhenAll()
、Wait()
等方法定义任务之间的依赖关系,以及在不同任务完成后执行的操作。这种任务组合方式使并发编程更加灵活且易于管理。CancellationToken
的概念,可用于取消任务的执行,从而更好地控制并发操作。所以,尽管ThreadPool在某些情况下仍然有其用途,但在C#编程中,使用Task替代ThreadPool已变为通用实践,推荐优先考虑使用Task来处理并发任务。
❝以上部分内容引用自 《编写高质量代码:改善C#程序的157个建议》 / 陆敏技著.一北京:机械工业出版社,2011.9
本文分享自 Niuery Diary 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有