C# 深入理解事件(event)机制

目录

一,引言

二,事件的定义和用法

2.1 同步事件执行 

2.2 异步事件执行

2.3 等待异步事件完成

2.4 捕获异常处理中的异常


一,引言

都知道事件的本质是一个多播委托(MulticastDelegate),但对于事件的机制和用法一直懵懵懂懂,本篇主要对此进行深入分析,首先要明确关于事件的疑惑:

  • Event 是同步还是异步执行的?(答:同步执行)

  • 如果是多个订阅,事件执行的顺序是什么?(答:串行执行)

  • 如果事件执行中发生异常,会发生什么事情?(答:如果一个订阅者(事件)发生异常。未执行的事件不会继续执行)

  • 事件支持异步执行吗?(答:支持)

  • 事件触发后,跨进程可以触发到吗?(答:可以)

二,事件的定义和用法

事件作为类的成员,一般是通过事件向其他类或对象通知发生的相关事情。 发送事件的类称为发布者,接收事件的类称为订阅者。

  • 发布者确定何时引发事件;订阅者确定对事件作出何种响应

  • 一个事件可以有多个订阅者。 订阅者可以处理来自多个发行者的多个事件。

  • 没有订阅者的事件永远也不会引发。

  • 事件通常用于表示用户操作,例如单击按钮或图形用户界面中的菜单选项。

  • 当事件具有多个订阅者时,引发该事件时会同步调用事件处理程序。 也可通过async/await达到异步调用事件的作用。

  • 在 .NET 类库中,事件基于 EventHandler 委托和 EventArgs 基类。

2.1 同步事件执行 

定义一个Demo类,其内部有个事件是 DemoEvent,我们给他开放了一个接口Raise,如果谁敢调用它,那么,它就触发报警事件DemoEvent

        public class Demo
        {
            public event EventHandler DemoEvent;
            public void Raise()
            {
                try
                {
                    this.DemoEvent?.Invoke(this, EventArgs.Empty);
                    Console.WriteLine("所有的事件处理已经被执行!");
                }
                catch (Exception ex)
                {

                }
            }
        }

随后在主程序中对事件进行订阅(这里采用了匿名方法进行订阅):

 static void Main(string[] args)
        {
            var instance = new Demo();
            instance.DemoEvent += (sender, args) =>
            {
                Console.WriteLine("执行事件1!");
            };
            instance.DemoEvent += (sender, args) =>
            {
                Console.WriteLine("执行事件2!");
            };
            Console.WriteLine("*开始发起事件!");
            instance.Raise();
            Console.WriteLine("*事件执行完毕,继续下一项工作!");
            Console.ReadLine();
        }

输出结果:

可以看到,事件是一次同步执行的(执行过程也会阻塞主线程)。

2.2 异步事件执行

在上面代码基础上,增加异步方法然后订阅:

 结果输出:

可以看的,新增加的异步事件处理,的确是第一个被触发的,只不过它没有阻塞主线程处理。

小知识点:

  • 在异步编程中虽然不推崇定义一个类似的async void xxxx(){}函数,因为这样的函数无法被主程序捕获结果或异常。 但凡是总有例外,而这个异步事件处理恰恰就是这个函数的最佳使用场景。
  • 上述代码是非UI编程,有关UI处理(按钮点击事件等),机制并不一样,UI为它的异步事件提供了一个SynchronizationContext,使它们能够在UI线程上恢复。它从不“等待”事件。

2.3 等待异步事件完成

虽然2.2完成了异步事件的执行,但是在上面的输出结果中,存在一个问题:

*开始发起事件!
异步事件1执行开始
执行事件1!
执行事件2!
所有的事件处理已经被执行!
*事件执行完毕,继续下一项工作!
异步事件1执行完毕

[异步事件1执行完毕]应该在[*事件执行完毕,继续下一项工作!]前面输出才符合逻辑。但是异步执行的事件是不阻塞主线程的,那么如何让主线程等待异步事件的完成呢

这就涉及到异步编程async/await内部机制的问题了,因此我们需要引入SynchronizationContext的内容,自定义一个继承类,来实现相关的操作:

        public class Demo
        {
            public event EventHandler DemoEvent;
            public void Raise()
            {
                try
                {
                    //3修改Raise函数,让事件的触发处在我们自定义的同步上下文内。
                    this.DemoEvent?.NaiveRaiseAsync(this, EventArgs.Empty).GetAwaiter().GetResult();
                    Console.WriteLine("所有的事件处理已经被执行!");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("事件处理中发生异常!", ex.Message);
                }
            }
        }
        //主程序调用
        static void Main(string[] args)
        {
            var instance = new Demo();
            //采用匿名订阅异步事件
            instance.DemoEvent += async (sendr, args) =>
            {
                Console.WriteLine("异步事件1执行开始");
                await Task.Delay(10);
                Console.WriteLine("异步事件1执行结果");

            };
            //传统的订阅异步事件
            instance.DemoEvent += method2;
            instance.DemoEvent += (sender, args) =>
            {
                Console.WriteLine("执行事件1!");
            };
            instance.DemoEvent += (sender, args) =>
            {
                Console.WriteLine("执行事件2!");
            };
            Console.WriteLine("*开始发起事件!");
            instance.Raise();
            Console.WriteLine("*事件执行完毕,继续下一项工作!");
            Console.ReadLine();
        }
        
        //异步方法
        static async void method2(object sender, EventArgs e)
        {
            Console.WriteLine("异步事件2执行开始");
            await Task.Delay(100);
            Console.WriteLine("异步事件2执行完毕");
        }

        //1实现同步上下文(对异步的分裂点进行标记)
        public class NaiveSynchronizationContext:SynchronizationContext
        {
            private readonly Action completed;
            public NaiveSynchronizationContext(Action completed)
            {
                this.completed = completed;
            }
            public override SynchronizationContext CreateCopy()
            {
                return new NaiveSynchronizationContext(this.completed);
            }
            public override void OperationStarted()
            {
                Console.WriteLine("同步上下文: 开始");
            }
            public override void OperationCompleted()
            {
                Console.WriteLine("同步上下文: 完成");
                this.completed();
            }
        }
    }
    //2对NaiveExtension函数进行扩展
    public static class NaiveExtension
    {
        public static Task NaiveRaiseAsync(this EventHandler @this, object sender, EventArgs eventArgs)
        {
            // 如果没有事件处理,那么立即结束
            if (@this == null)
            {
                return Task.CompletedTask;
            }
            var delegates = @this.GetInvocationList();
            var count = delegates.Length;

            var tcs = new TaskCompletionSource<bool>();
            foreach (var @delegate in @this.GetInvocationList())
            {
                // 检查AsyncStateMachineAttribute属性,判断是否异步处理函数
                var async = @delegate.Method.GetCustomAttributes(typeof(AsyncStateMachineAttribute), false).Any();

                // 定义 'completed' action
                var completed = new Action(() =>
                {
                    if (Interlocked.Decrement(ref count) == 0)
                    {
                        tcs.SetResult(true);
                    }
                });

                if (async)
                {
                    SynchronizationContext.SetSynchronizationContext(new NaiveSynchronizationContext(completed));
                }

                @delegate.DynamicInvoke(sender, eventArgs);

                if (!async)
                {
                    // 如果不是异步,手工调用完成
                    completed();
                }
            }
            return tcs.Task;
        }
    }

订阅了两个异步事件,两个同步事件,结果如下:

2.4 捕获异常处理中的异常

我们知道,在事件执行过程中,如果某个事件发生异常,就会终止未执行的事件:

 这里的原因是:

在基本synchronnizationcontext类中,Send和Post方法是使用应用程序ThreadPool实现的。因此,在事件处理程序中抛出的异常,实际上在打印上述消息的ThreadPool线程中抛出。

那么我们可以尝试重载 Post和Send看看。

    //1实现同步上下文(对异步的分裂点进行标记)
    public class NaiveSynchronizationContext : SynchronizationContext
    {
        private readonly Action completed;
        private readonly Action<Exception> failed;
        public NaiveSynchronizationContext(Action completed, Action<Exception> failed)
        {
            this.completed = completed;
            this.failed = failed;
        }
        public override void Post(SendOrPostCallback d, object state)
        {
            if (state is ExceptionDispatchInfo edi)
            {
                Console.WriteLine("正捕获异常");
                this.failed(edi.SourceException);
            }
            else
            {
                Console.WriteLine("Posting");
                base.Post(d, state);
            }
        }
        public override void Send(SendOrPostCallback d, object state)
        {
            if (state is ExceptionDispatchInfo edi)
            {
                Console.WriteLine("正捕获异常");
                this.failed(edi.SourceException);
            }
            else
            {
                Console.WriteLine("Sending");
                base.Send(d, state);
            }
        }
        public override SynchronizationContext CreateCopy()
        {
            return new NaiveSynchronizationContext(this.completed, this.failed);
        }
        public override void OperationStarted()
        {
            Console.WriteLine("同步上下文: 开始");
        }
        public override void OperationCompleted()
        {
            Console.WriteLine("同步上下文: 完成");
            this.completed();
        }
    }

    //2对NaiveExtension函数进行扩展
    public static class NaiveExtension
    {
        public static Task NaiveRaiseAsync(this EventHandler @this, object sender, EventArgs eventArgs)
        {
            // 如果没有事件处理,那么立即结束
            if (@this == null)
            {
                return Task.CompletedTask;
            }
            var delegates = @this.GetInvocationList();
            var count = delegates.Length;

            var tcs = new TaskCompletionSource<bool>();
            var exception = (Exception)null;
            foreach (var @delegate in @this.GetInvocationList())
            {
                // 检查AsyncStateMachineAttribute属性,判断是否异步处理函数
                var async = @delegate.Method.GetCustomAttributes(typeof(AsyncStateMachineAttribute), false).Any();

                // 定义 'completed' action
                var completed = new Action(() =>
                {
                    if (Interlocked.Decrement(ref count) == 0)
                    {
                        if (exception is null)
                        {
                            tcs.SetResult(true);
                        }
                        else
                        {
                            tcs.SetException(exception);
                        }
                    }
                });
                var failed = new Action<Exception>(e =>
                {
                    Interlocked.CompareExchange(ref exception, e, null);
                });
                if (async)
                {
                    SynchronizationContext.SetSynchronizationContext(new NaiveSynchronizationContext(completed, failed));
                }

                try
                {
                    @delegate.DynamicInvoke(sender, eventArgs);
                }
                catch (TargetInvocationException e)
                when (e.InnerException != null)
                {
                    failed(e.InnerException);
                }
                catch (Exception e)
                {
                    failed(e);
                }

                if (!async)
                {
                    // 如果不是异步,手工调用完成
                    completed();
                }
            }
            return tcs.Task;
        }
    }

最终输出结果:

可以看到的,这里的实现剔除了短路行为,即使你的某个处理函数有异常,它依然可以向下分发事件。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/42044.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

S3C2440点亮LED(裸机开发)

文章目录 前言一、环境介绍一、GPIO介绍二、点亮开发板的LED1.预备动作2.led代码 总结 前言 本期和大家主要分享的是使用S3C2440开发板点亮一个LED灯&#xff0c;可能大家拿到开发板之后做的第一件事情都是点灯&#xff0c;这是为什么呢&#xff1f;因为点灯这件事情不仅能够检…

Visual Studio 向工程中添加现有文件夹

前言&#xff1a; 在创建C#类库&#xff08;dll&#xff09;工程后&#xff0c;需要把现有的C#文件添加进工程中 步骤1.将所有文件夹复制到工程中 步骤2. 点击这个图标&#xff0c;显示所有文件夹 工程目录下的所有文件夹都会被显示出来 选中需要添加的文件夹&#xff0c;右…

微信小程序下拉刷新获取数据和触底事件刷新实现

一、下拉刷新 1.json文件 说明&#xff1a;开启下拉刷新&#xff0c;然后设置窗口的背景色&#xff0c;方便观看。 "enablePullDownRefresh": true,"backgroundColor":"#FFC0CB" 2. js文件 说明&#xff1a;重新发起请求&#xff0c;并显示加…

在Redis主从系统中使用哨兵

一、什么是哨兵 Redis的哨兵&#xff08;Sentinel&#xff09;是Redis分布式系统中的一种特殊角色&#xff0c;用于监控和管理Redis主从复制架构中的主节点&#xff08;master&#xff09;和从节点&#xff08;slave&#xff09;。 哨兵的主要功能是确保Redis系统的高可用性。它…

vscode debug的方式

在.vscode文件夹下建立launch.json 例子1&#xff1a;调试python 来自 https://github.com/chunleili/tiPBD/tree/amg {"version": "0.2.0","configurations": [{"name": "hpbd 5 5","type": "python&quo…

26.JavaWeb-SpringSecurity安全框架

1.SpringSecurity安全框架 Spring Security是一个功能强大且灵活的安全框架&#xff0c;它专注于为Java应用程序提供身份验证&#xff08;Authentication&#xff09;、授权&#xff08;Authorization&#xff09;和其他安全功能。Spring Security可以轻松地集成到Spring框架中…

极值理论 EVT、POT超阈值、GARCH 模型分析股票指数VaR、条件CVaR:多元化投资组合预测风险测度分析...

全文链接&#xff1a;http://tecdat.cn/?p24182 本文用 R 编程语言极值理论 (EVT) 以确定 10 只股票指数的风险价值&#xff08;和条件 VaR&#xff09;&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 使用 Anderson-Darling 检验对 10 只股票的组合数据进行…

uniapp 小程序 联想地址搜索

效果图&#xff1a; qqmap-wx-jssdk.js下载 <template><view class"items"><view class"items-text">地址&#xff08;必填&#xff09;</view><input type"text" placeholder"搜索地址" maxlength&quo…

解决Missing cookie ‘JssionId‘ for method parameter of type String问题

错误描述如下所示&#xff1a; 上述错误是我在使用CookieValue注解&#xff0c;获取cookieID时出现的&#xff0c;错误原因是由于**CookieValue注解注解中的value值和浏览器中的cookie的jssionID不一致所导致的** 如下所示为浏览器中的CookieID的参数名 而我在注解中写的如下图…

自动化测试之数据驱动与关键字驱动

目录 1.录制/回放的神话 2.数据驱动的自动化测试框架 3.关键字驱动的自动化测试 初次接触自动化测试时&#xff0c;对数据驱动和关键字驱动不甚理解&#xff0c;觉得有点故弄玄须&#xff0c;不就是参数和函数其嘛&#xff01;其实其也体现了测试所不同与开发的一些特点&…

基于遗传算法的新能源电动汽车充电桩与路径选择MATLAB程序

主要内容&#xff1a; 根据城市间的距离&#xff0c;规划新能源汽车的行驶路径。要求行驶距离最短。 部分代码&#xff1a; %% 加载数据 %%遗传参数 load zby;%个城市坐标位置 NIND50; %种群大小 MAXGEN200; Pc0.9; %交叉概率 Pm0.2; %变异概率 GGAP0.…

postman 自动化测试

postman 自动化测试 0、写在前面1、变量引用1.1、如何在请求体中引用变量 2、变量设置2.1、测试需求场景描述&#xff1a;2.2、postman实战2.2.1、全局token的处理2.2.2、接口1的处理2.2.3、接口2的处理2.2.4、接口3的处理 3、测试结果展示 0、写在前面 在有些时候看官方文档 …

聚焦型光场相机基于立体视差的深度估计原理

聚焦型光场相机可以看作是主透镜将物面成了一个放大或者缩小的虚像&#xff0c;然后每个微透镜阵列对这个经过放大或者缩小的虚像进行二次成像后投影在了ccd平面&#xff0c;其中二次成像的过程可以比拟为一个虚拟阵列相机&#xff0c;利用MLA和主透镜的相关参数就可以以立体视…

Android系统开发-入门篇

参见&#xff1a;[视频教程] 写给应用开发的 Android Framework 教程——玩转 AOSP 篇之 Android 系统开发工具推荐 - 掘金 前置条件&#xff1a; android系统源码位于 linux 服务器&#xff0c;ssh 地址假如为&#xff1a;test172.1.10.2本机为windows 1、本机&#xff1a; 下…

访问Liunx文件系统

访问Liunx文件系统 识别文件系统和设备 存储管理概念 Linux服务器上文件按文件系统层次结构访问。该文件系统层次结构测试由系统可用的存储设备所提供的文件系统组装而来。每个文件系统都是一个已格式化的存储设备&#xff0c;可用于存储文件。 文件系统和挂载点 要让文件系…

【mysql】—— 数据库的操作

序言&#xff1a; 在上篇文章我已经对数据库进行了详细的介绍&#xff0c;接下来我们就将上手学习操作的细节了。本篇文章便带领大家去学习有关库操作的基本知识&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;库的操作 1、 创建数据库 2、字符集和…

HTML input text 常用事件

前言 用于记录开发中常用到的&#xff0c;快捷开发 简单实例 <input type"text" name"noSecretKeyJson" maxlength"200" />常用事件 oninput &#xff08;在用户输入时触发&#xff09;及案例 案例一&#xff1a;限制只允许输入数字…

C#基础--反射

反射 一、为什么学习反射 因为反射真的是无处不在&#xff0c;ORM、MVC、IOC、AOP、Attribute等等都会使用到反射。反射是程序员的快乐 二、什么是反射 Ilspy&#xff1a;逆向工程&#xff0c;可以吧DLL/Exe文件反编译回来 DLL/EXE 文件下包含Metadata和IL&#xff0c;IL是对…

跨文化合作:如何解决海外网红营销中的文化差异?

随着社交媒体的快速发展&#xff0c;海外网红营销已成为许多品牌和企业获取国际市场的有效方式。然而&#xff0c;由于不同国家和地区存在着独特的文化差异&#xff0c;如语言、价值观、习俗等&#xff0c;这也给品牌进行海外网红营销带来了一系列挑战。本文Nox聚星将和大家探讨…

WPF 自定义控件完成库容表盘显示效果

先看一下显示效果&#xff1a; 需要注意的地方有以下几点&#xff1a; 表盘的刻度分部&#xff0c;长刻度和短刻度显示。在数值80W时&#xff0c;需要更改刻度盘的颜色渐变。在数值80W时&#xff0c;更改库容总数背景的显示&#xff0c;也是颜色渐变。刻度盘控件属性定义&…