C#线程间通信-事件

事件(Event)是一种使对象或类能够提供通知的成员,可以用来实现在线程间通信。这在GUI程序中很常见。为了UI的流畅,通常会把耗时的任务放到单独的线程上执行,等任务执行结束后把结果更新到UI上。

案例说明

点击按钮,开始下载(模拟耗时工作),下载进度更新到进度条上。 .

C#线程间通信-事件

代码结构

C#线程间通信-事件

声明事件 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));
}

日志打印

C#线程间通信-事件

  • • 按钮执行方法 ExecuteStart 很快就执行结束,不会阻塞UI线程

  • • 耗时任务 Download 在单独的线程上执行

  • • Task.ContinueWith 默认会在新的线程上执行(可以改变)

委托链

如果事件订阅两次,会发生什么呢?

public MainWindowViewModel(IBusinessService businessService)
{
    _service = businessService;
    // 订阅事件
    _service.DownloadFileProgressEvent += _service_DownloadFileProgressEvent;
    // 再次订阅事件
    _service.DownloadFileProgressEvent += _service_DownloadFileProgressEvent;

    // 按钮点击命令
    StartBtnCommand = new DelegateCommand(ExecuteStart);
}

从日志可以看出,event 回调方法会执行两次。

C#线程间通信-事件

当事件的使用者订阅该事件时,其本质就是将事件的处理方法加入到委托链中。订阅事件的本质就是调用 Delegate 的 Combine 方法将事件处理方法加入到委托链中。