事件(Event)是一种使对象或类能够提供通知的成员,可以用来实现在线程间通信。这在GUI程序中很常见。为了UI的流畅,通常会把耗时的任务放到单独的线程上执行,等任务执行结束后把结果更新到UI上。
案例说明
点击按钮,开始下载(模拟耗时工作),下载进度更新到进度条上。 .
代码结构
声明事件 DownloadFileProgressEvent
/// <summary>
/// 业务类
/// </summary>
public class BusinessService : IBusinessService
{
/// <summary>
/// 声明event
/// </summary>
public event EventHandler DownloadFileProgressEvent;
/// <summary>
/// 模拟耗时任务接口
/// </summary>
public void DownloadFile()
{
int totleTime = 0;
// 模拟5s下载完成
while (totleTime < 5)
{
totleTime += 1;
// 模拟耗时任务
Thread.Sleep(1000);
// 触发进度更新
DownloadFileProgressEvent.Invoke(this, new DownloadProgressEventArgs(totleTime * 20));
}
}
}
订阅事件 DownloadFileProgressEvent
public MainWindowViewModel(IBusinessService businessService)
{
_service = businessService;
// 订阅事件
_service.DownloadFileProgressEvent += _service_DownloadFileProgressEvent;
// 按钮点击命令
StartBtnCommand = new DelegateCommand(ExecuteStart);
}
/// <summary>
/// 按钮点击事件
/// </summary>
private void ExecuteStart()
{
Console.WriteLine(string.Format("begin task : thread = {0}", Thread.CurrentThread.ManagedThreadId));
// 开启新的线程执行耗时任务
Task.Run(() => {
_service.DownloadFile();
}).ContinueWith(t => {
Console.WriteLine(string.Format("task continue : thread = {0}", Thread.CurrentThread.ManagedThreadId));
});
Console.WriteLine(string.Format("end task : thread = {0}", Thread.CurrentThread.ManagedThreadId));
}
/// <summary>
/// event 回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _service_DownloadFileProgressEvent(object sender, System.EventArgs e)
{
ProgressBarValue = ((DownloadProgressEventArgs)e).ProgressValue;
Console.WriteLine(string.Format("event-callback : sender = {0}, eventArgs = {1}, thread = {2}", sender, e, Thread.CurrentThread.ManagedThreadId));
}
日志打印
-
• 按钮执行方法 ExecuteStart 很快就执行结束,不会阻塞UI线程
-
• 耗时任务 Download 在单独的线程上执行
-
• Task.ContinueWith 默认会在新的线程上执行(可以改变)
委托链
如果事件订阅两次,会发生什么呢?
public MainWindowViewModel(IBusinessService businessService)
{
_service = businessService;
// 订阅事件
_service.DownloadFileProgressEvent += _service_DownloadFileProgressEvent;
// 再次订阅事件
_service.DownloadFileProgressEvent += _service_DownloadFileProgressEvent;
// 按钮点击命令
StartBtnCommand = new DelegateCommand(ExecuteStart);
}
从日志可以看出,event 回调方法会执行两次。
当事件的使用者订阅该事件时,其本质就是将事件的处理方法加入到委托链中。订阅事件的本质就是调用 Delegate 的 Combine 方法将事件处理方法加入到委托链中。