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

news2024/12/30 3:07:11

目录

一,引言

二,事件的定义和用法

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;
        }
    }

最终输出结果:

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【代码随想录 | Leetcode | 第六天】链表 | 反转链表 | 两两交换链表中的节点 | 删除链表的倒数第 N 个结点

前言 欢迎来到小K的Leetcode|代码随想录|专题化专栏&#xff0c;今天将为大家带来反转链表、两两交换链表中的节点和删除链表的倒数第N个节点的分享✨ 目录 前言206. 反转链表24. 两两交换链表中的节点19. 删除链表的倒数第 N 个结点总结 206. 反转链表 ✨题目链接点这里 给你…

HDFS与MapResource笔记

客户端向NN请求上传文件 NN回应可以上传 请求上传块,返回DN 所以后面就比较慢 找最近的服务器进行 64K发到1节点,1节点立刻发给2节点,同时1节点自动开始落盘,这里,3个节点是同时落盘的. 因为缓存是在内存中,而持久化是将数据存到磁盘上. 副本节点选择: 1.安全:放不同机架 2.速…

QT中QTimer的循环时间与槽函数执行时间分析,以及在事件循环中触发

目录 当循环时间小于槽函数时间时&#xff1a; 当循环间隔时间大于槽函数时间时&#xff1a; 当存在两个定时器器&#xff0c;其中一个还是间隔100ms&#xff0c;另一个间隔1000ms&#xff1a; 当两个定时器的循环周期大于槽函数执行时间时 当在主程序中添加一个for循环…

13年测试老鸟总结,性能测试-并发用户数估算(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 并发用户数&#…

nuxt3初始化项目后配置eslint+prettier

1、安装 eslint 、eslint-config-prettier、eslint-plugin-prettier、prettier arn add eslint eslint-config-prettier eslint-plugin-prettier prettier -D eslint-config-prettier&#xff1a;关闭eslint中与prettier相互冲突的规则。eslint-plugin-prettier&#xff1a;赋…

26.Java 异常

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的 要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常: 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个…

【Whisper】《OpenAI Whisper 精读【论文精读】》学习笔记

方法 Whisper在论文中表示使用单模型&#xff08;single model&#xff09;来完成多个语音任务&#xff08;multitask&#xff09;&#xff0c;李沐教授认为优点是设计上比较干净&#xff1b; I. 关于单模型效果的疑问 但是他同时也提出了两个疑问&#xff1a; 使用单模型会…

Simulink仿真模块 - Data Store Read

Data Store Write:向数据存储中写入数据 库:Simulink / Signal Routing 模型为: 说明 Data Store Write 模块将其输入端口的值复制到指定的数据存储中。Data Store Write 模块执行的每个写入操作将覆盖数据存储,取代以前的内容。 此模块写入的数据存储由定义数据存储的 Dat…

刷题日记08《BFS》

概念 宽度优先搜索算法&#xff08;又称广度优先搜索&#xff09;是最简便的图的搜索算法之一&#xff0c;这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS&#xff0c;属于一种盲目搜寻…

易语言后,极语言成为官方支持语言,新中文编程语言开始革新而来

易语言促进了中文编程普及和发展 众所周知&#xff0c;吴涛先生在1997年创建的易语言称得上是中文编程的鼻祖。 易语言之后&#xff0c;很多中文编程语言接踵而至&#xff1a;甲语言、乙语言、丙语言、甲骨文编程语言、梅花编程语言等等。 为中国的软件开发者提供了更加便捷和…

什么是aPaaS?aPaaS和低代码是一回事吗?

低代码和aPaaS是近年最为火热的技术趋势之一&#xff0c;那么低代码aPaaS吗&#xff1f;两者有什么关系&#xff1f;今天小帆为大家介绍它们的前世今生。 在介绍低代码和aPaaS前&#xff0c;先要明确一个概念——云服务。云服务是基于互联网的相关服务的增加、使用和交互模式&a…

Profibus-DP转modbus RTU网关profibus主站的实现方案有哪些

远创智控YC-DPM-RTU网关在Profibus总线侧实现主站功能&#xff0c;在Modbus串口侧实现从站功能。可将ProfibusDP协议的设备&#xff08;如&#xff1a;EH流量计、倍福编码器等&#xff09;接入到Modbus网络中&#xff1b;通过增加DP/PA耦合器&#xff0c;也可将Profibus PA从站…

如何获取microstore商品详情接口php接口jason数据字段

随着科技的发展&#xff0c;API接口成为了各行业发展的最新趋势。在微店购物平台中&#xff0c;商品详情API接口的引入&#xff0c;为商家和消费者提供了更加便捷、高效的用户体验。本文将为大家详细介绍微店商品详情API接口的优势和使用方法 商品详情API接口的优势 1.提升用户…

那些隐藏在项目中的kotlin小知识,在座各位...

写kotlin越来越久&#xff0c;很多代码虽然能看懂&#xff0c;并且能去改&#xff0c;但是不知道他用了啥&#xff0c;里面的原理是什么&#xff0c;举个例子&#xff1f;大家一起学习一下吧 内联函数 顾名思义&#xff0c;但是在项目中我遇到得很少&#xff0c;他比较适用于一…

基于互联网下的智能配电网运维

安科瑞 华楠 摘要&#xff1a;在“互联网 ”背景下实现互联网技术和智能配电网的融合成为一种必然趋势&#xff0c;本文主要针对基于“互联网 ”的智能配电网运维技术进行分析&#xff0c;在分析我国目前智能电网运维困境和现状的基础上&#xff0c;构建基于“互联网 ”的智能配…

【域名解析】 【二级域名分发】 【vue3部署】 【腾讯云cos】 【DNSPOD】

首先注册腾讯云账号登录 先在腾讯云域名系统中购买域名并且在工信部备案 我的域名 - DNSPod-免费智能DNS解析服务商-电信_网通_教育网,智能DNS 然后点击 DNSPOD DNS 解析 - 定价中心 - 腾讯云 DNSPod - 国民级 DNS 服务平台_智能DNS_免费_域名_解析_VIP_套餐 自行选择免费版…

【UE4 塔防游戏系列】09-防御塔升级、击杀敌人增加金钱

目录 效果 步骤 一、控件蓝图文本控件内容绑定金钱数 二、防御塔改造 三、击杀敌人增加金钱 四、防御塔升级功能 效果 步骤 一、控件蓝图文本控件内容绑定金钱数 1. 打开“TaFangGameMode”&#xff0c;新增一个变量命名为“PlayerMoney”&#xff0c;默认值设为2…

dp算法 力扣152乘积最大子数组

本文是Java代码&#xff01;&#xff01; 152. 乘积最大子数组 - 力扣&#xff08;LeetCode&#xff09; 一、题目详情 给你一个整数数组 nums &#xff0c;请你找出数组中乘积最大的非空连续子数组&#xff08;该子数组中至少包含一个数字&#xff09;&#xff0c;并返回该…

【C++】STL栈和队列基本功能介绍、题目练习和模拟实现(容器适配器)

stack && queue 基本功能介绍、练习和模拟实现 前言正式开始基本函数功能三道经典栈题目讲解最小栈栈的弹出压入顺序逆波兰表达式求值 模拟实现stackqueue deque 前言 本篇基本功能不会介绍太多&#xff0c;主要是说一下STL库中的接口&#xff0c;还是在这个网站上的&…

【动态内存错误详解和C的内存分区】

常见的动态内存错误 1.动态内存错误2.经典案例分析2.1案例一2.1.1**问题分析**2.1.2**修改错误** 2.2案例二2.2.1 原因分析2.2.2 解决问题 c/c内存分布1.2 内存分区简介1.2.1 栈区(stack)1.2.2 堆区(heap)1.2.3 全局(静态)区1.2.4 常量区1.2.5 代码区 1.动态内存错误 &#xf…