为了实现WPF界面下的断点续传等下载功能,从Github上找到这个项目: https://github.com/markodt/SGet
里面的下载类使用了一些事件
我以此来学习事件的用法
首先C#中没有事件这个类型,事件是一个特殊的委托。
下面是一个简单事件声明(没有使用参数
//声明下载完成事件 public event EventHandler DownloadCompleted; //定义事件触发函数 protected virtual void RaiseDownloadCompleted() { if (DownloadCompleted != null) { //注意这里的EventArgs.Empty DownloadCompleted(this, EventArgs.Empty); } } //在某处使用触发函数,触发事件 public void Start(){ this.RaiseDownloadCompleted(); } //其他地方有需要时就调用事件 WebDownloadClient download = new WebDownloadClient(url); download.DownloadCompleted += download.DownloadCompletedHandler;
然而上述的做法并不安全,参考:
//3.定义触发事件的方法 protected virtual void OnNewMail(NewMailEventArgs e) { /* 第1种做法 if(this.NewMail != null) { this.NewMail(this,e); } */ /* 第二种做法 EventHandler<NewMailEventArgs> temp = this.NewMail; if (temp != null) { temp(this, e); } */ //第三种做法 EventHandler<NewMailEventArgs> temp = Interlocked.CompareExchange(ref this.NewMail, null, null); if (temp != null) { temp(this, e); } }
第一种做法是很常见的做法,判断不为空,然后就触发。CLR里提到这是线程不安全的做法,因为单我们判断不为空后,准备执行时,另一个线程将从委托链将委托移除,此时变成了空,引发NullReferenceException异常。
来自 https://www.cnblogs.com/ldyblogs/p/event.html
第二、三种做法都是线程安全的,因为它通过一个临时委托变量(委托链保存了所有委托),通过上一篇对委托链的了解,我们知道对委托链进行Combine/Remove实际都会创建一个新的数组对象,此时对temp没有影响。但实际上事件主要在单线程的环境下使用,所以一般也不会出现这种问题。