这是弹幕派开发文档系列的第一篇!在开发弹幕派的过程中,通过网上的资料、MSDN学习到了很多WPF和C#的相关知识,在这里一并写出来,希望能够帮助到C#特别是WPF开发者。
弹幕派是我们开发的一个桌面弹幕小程序,说它小,但是它的开发周期可不短,在开发过程中学到了很多东西,今天我要说的便是第一个,如何运用后台进程连接网络。
浅谈BackgroundWorker在WPF中的使用
弹幕派在刚开始UI的渲染(即弹幕的产生和刷新)以及弹幕内容的获取都是在一个进程中完成的,这样导致一个问题就在于每当从网络获取数据时就会出现明显卡顿,如果网络失去连接就会导致程序假死无法继续进行。很明显这样是不行的,因此必须要引入多线程,通过后台线程获取数据,再将数据更新到UI中。
在WPF中,为了保证线程安全,Windows只允许创建UI元素的线程访问这些元素。如果在其他线程中尝试修改UI元素的属性,就会触发STA错误
,导致程序崩溃。这样做是为了保证内容渲染的一致性。但是也会导致一个问题——我们无法通过后台线程直接修改UI元素的属性。WPF通过Dispatcher
机制解决了这一问题。WPF为UI渲染设置了一个Dispatcher
,这个Dispatcher
我们可以理解为调度员,它与UI渲染相关的事件排成一个队列,按优先级对其队列中的元素进行排序,并且按序执行,这样可以保证UI在渲染时只执行一个任务,保证UI内容的一致性。如果我们的后台线程需要对界面元素的属性进行修改,可以请求UI线程代替它完成这一操作。那么如何请求UI线程帮忙呢?通过向Dispatcher
注册工作项,将想要执行的任务加入队列,这个任务会在某个时间由Dispatcher
完成,后台进程无需插手UI渲染。
Dispatcher类提供两种调用方法,一种是Invoke
同步调用,调用方必须等待UI进程完成这一任务才会返回并继续下面的操作;另一种是BeginInvoke
异步调用,调用方在调用后会立即返回。
在弹幕派原有的代码中对这一部分有所使用。原本弹幕派刷新弹幕是通过每秒钟定时修改所有弹幕TextBlock的Margin属性的值达到移动弹幕的效果,那么在计时器Timer的Elapse
事件触发的函数中,如果直接修改这些Margin会触发STA错误。因此需要通过BeginInvoke
来执行这一操作。
1 | private delegate void DispatcherDelegateTimer(); // 声明委托 |
使用后台进程有三种方式,第一种是Task,第二种是Thread,第三种就是我们今天要介绍的BackgroundWorker了。这三种方法各有千秋,但是BackgroundWorker
更适合用于实现后台连接网络下载,因此在弹幕派的弹幕获取、自动更新等地方都主要使用了BackgroundWorker
。
那么如何用BackgroundWorker
实现后台连接网络获取数据呢?
首先我们需要引入命名空间
1 | using System.ComponentModel; |
之后我们需要添加BackgroundWorker
组件,这一组件可以从Xaml界面添加——从工具箱中的“组件”选项卡中,添加BackgroundWorker
组件;也可以在代码中声明:
1 | private BackgroundWorker fetchBW = new BackgroundWorker(); |
之后在初始化过程中设置BackgroundWorker
的属性,可以在构造函数中,也可以在Loaded
函数中。
1 | fetchBW.WorkerReportsProgress = true; //是否报告工作进度 |
首先要设置是否报告工作进度,如果WorkerReportsProgress
为true,则可以在ProgressChanged
事件的函数中处理进度条等信息。当然Progress的数值要自行在DoWork
函数中利用ReportProgress
设置数值的变化(例如获取已经下载的进度并更新进度条)。
1 | private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) |
如果允许异步取消(WorkerSupportsCancellation = false
),则通过CancelAsync
可以取消工作。此时CancellationPending = true
。
之后再绑定DoWork
、ProgressChanged
、RunWorkerCompleted
事件。DoWork
里写明主要功能,同时需要回报进度和处理取消事件。ProgressChanged
里根据进度处理事件(修改进度条等),RunWorkerCompleted
事件处理DoWork
的结果。
那么如何在RunWorkerCompleted
中获取DoWork
的结果呢?
1 | private void FetchBW_DoWork(Object sender, DoWorkEventArgs e) { |
将结果保存至DoWork
的e.Result
中,之后可以在RunWorkerCompleted
的e.Result
中获取到结果。处理结果时要处理Cancelled(取消事件)和Error(错误事件)。
这里要注意的是,对于网络访问等操作来说,很有可能会出现网络连接中断导致超时,因此这个时候需要我们设置一个定时器,在开始处理事件前启动定时器,然后在定时器超时时调用CancelAsync
即可。
BackgroundWorker
不仅可以在WPF中调用,在WinForm中也可以。BackgroundWorker
最适合的场景便是后台下载,通过DoWork
、ReportProgress
和RunWorkerCompleted
三者分开,可以明确地划分执行工作、更新界面、处理结果三个部分,与定时器Timer
和按钮Button
结合使用还可以保证程序不会由于网络连接中断等原因一直卡住。