为了实现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异常。
第二、三种做法都是线程安全的,因为它通过一个临时委托变量(委托链保存了所有委托),通过上一篇对委托链的了解,我们知道对委托链进行Combine/Remove实际都会创建一个新的数组对象,此时对temp没有影响。但实际上事件主要在单线程的环境下使用,所以一般也不会出现这种问题。

来自 https://www.cnblogs.com/ldyblogs/p/event.html

凡心所向,素履所往。