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

这篇具有很好参考价值的文章主要介绍了C# 深入理解事件(event)机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一,引言

二,事件的定义和用法

2.1 同步事件执行 

2.2 异步事件执行

2.3 等待异步事件完成

2.4 捕获异常处理中的异常

三,事件的综合案例

3.1 需求:汽车出停车场时收费,开闸放行


一,引言

都知道事件的本质是一个多播委托(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();
        }

输出结果:

C# 深入理解事件(event)机制,# C#高级篇,c#

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

2.2 异步事件执行

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

C# 深入理解事件(event)机制,# C#高级篇,c#

 结果输出:

C# 深入理解事件(event)机制,# C#高级篇,c#

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

小知识点:

  • 在异步编程中虽然不推崇定义一个类似的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;
        }
    }

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

C# 深入理解事件(event)机制,# C#高级篇,c#

2.4 捕获异常处理中的异常

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

C# 深入理解事件(event)机制,# C#高级篇,c#

 这里的原因是:

在基本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;
        }
    }

最终输出结果:

C# 深入理解事件(event)机制,# C#高级篇,c#

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

三,事件的综合案例

3.1 需求:汽车出停车场时收费,开闸放行

通过分析需求,可以明确通过线程模拟相机实时抓拍车牌(发布者),当抓拍到车牌说明来车了,即触发一下事件:收费员收费,闸门放行(订阅者),具体代码如下:

  public class CarInfo : EventArgs
    {
       //停车的开始时间
       public DateTime StartTime { get; set; }
        //停车的结束时间
        public DateTime EndTime { get; set; }
        //车牌
        public string LicensePlate { get; set; }
    }
    public class SnapInfo
    {
        //车牌标志,拍到车牌说明有车;反之无车
        public string LicensePlate { get; set; }
    }
    /// <summary>
    /// 发布者
    /// </summary>
    public class Camera
    {
        public event EventHandler<CarInfo> OnSnapLicenseEvent;
        //模拟摄像机循环在抓拍车牌
        public void SnapPhoto()
        {
            Task.Run(() =>
            {
                List<string> license = new List<string>()
                { "","","","沪A11111", "沪B22222","沪C33333","","" };
                Random random = new Random();
                while(true)
                {
                    Thread.Sleep(1000);
                    int index = random.Next(1, license.Count + 1);
                    SnapInfo snapInfo = new SnapInfo() { LicensePlate = license[index - 1] };
                    //当车牌不为空的时候表示车来了
                    if (!string.IsNullOrEmpty(snapInfo.LicensePlate))
                    {
                        Console.WriteLine($"抓拍到车牌{snapInfo.LicensePlate}!");
                        OnSnapLicense(GetCarInfoBySnapInfo(snapInfo));

                    }
                    else
                    {
                        Console.WriteLine("当前没有抓拍到车牌!");
                        Console.WriteLine("--------------------------------------");
                    }
                }
            });
        }
        public CarInfo GetCarInfoBySnapInfo(SnapInfo snapInfo)
        {
            //抓拍到车牌后,这里直接赋值,相当于模拟通过接口车牌查询了该车的进场数据
            CarInfo carInfo = new CarInfo()
            {
                StartTime = DateTime.Parse("2023-08-03 12:00:00"),
                EndTime = DateTime.Now,
                LicensePlate = snapInfo.LicensePlate,
            };
            return carInfo;
        }
        public void OnSnapLicense(CarInfo carInfo)
        {
            OnSnapLicenseEvent?.Invoke(this, carInfo);
        }
    }
/// <summary>
/// 订阅者
/// </summary>
    //收费员(负责收费)
    public class Charger
    {
        //收费
        public void Charge(object sender,CarInfo carInfo)
        {
            Console.WriteLine($"收费员:对{carInfo.LicensePlate}完成了收费");
        }
    }
    //闸机(负责开关)
    public class Gate
    {
        public void OpenGate(object sender,CarInfo carInfo)
        {
            Console.WriteLine($"闸机对:{carInfo.LicensePlate}车辆放行");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //先分析需求:车到车库门口,摄像机要拍照到车牌后,收费员收费,闸机抬杆
            Camera camera = new Camera();
            Charger charger = new Charger();
            Gate gate = new Gate();
            camera.OnSnapLicenseEvent += charger.Charge;
            camera.OnSnapLicenseEvent += gate.OpenGate;
            camera.SnapPhoto();
            Console.ReadLine();
        }

    }

结果输出:

C# 深入理解事件(event)机制,# C#高级篇,c#

 文章来源地址https://www.toymoban.com/news/detail-601449.html

到了这里,关于C# 深入理解事件(event)机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C#事件event

    事件模型的5个组成部分 事件拥有者(event source)(类对象)(有些书将其称为事件发布者) 事件成员(event)(事件拥有者的成员)(事件成员就是事件本身,事件不会主动发生,其只会在事件拥有者的内部逻辑的触发下发生。) 事件响应者(event subscriber)(类对象)(有

    2024年02月09日
    浏览(48)
  • c#事件(event)

    C#中的事件是一种特殊的委托,它用于实现观察者模式,允许对象在特定事件发生时通知其他对象。 以下是使用C#事件的示例: 首先,定义一个包含事件的类: 在上面的代码中,我们定义了一个EventPublisher类,其中包含一个名为MyEvent的事件。该事件基于EventHandler委托类型,它

    2024年02月10日
    浏览(44)
  • 【Spring底层原理高级进阶】轻松掌握 Spring MVC 的拦截器机制:深入理解 HandlerInterceptor 接口和其实现类的用法

     🎉🎉欢迎光临🎉🎉 🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀 🌟特别推荐给大家我的最新专栏 《Spring 狂野之旅:底层原理高级进阶》 🚀 本专栏纯属为爱发电永久免费!!! 这是苏泽的个人主页可以看到我其他的内容哦👇👇 努力的苏泽 http://suze

    2024年02月20日
    浏览(52)
  • C#中的委托(Delegate)和事件 (Event)详解与使用范例

    最近天气晴雨不定,你因为害怕打游戏时忘记在下雨时收衣服或者在天晴时把衣服挂出去,于是你委托好友小明在天气发生变化时打电话通知你,这就是一种委托. 下面是这种委托的实例代码 以上代码的输出为 值得一提的是,如下代码为创建一个继承自Delegate类的名为XiaoMing的子类

    2023年04月12日
    浏览(40)
  • 高级UI之Android事件分发机制原理及源码分析

    在 Android 中, 事件分发机制是一块很重要的知识点, 掌握这个机制能帮你在平时的开发中解决掉很多的 View 事件冲突问题,这个问题也是面试中问的比较多的一个问题了, 本篇就来总结下这个知识点。 Android 中页面上的 View 是以树型结构显示的,View 会重叠在一起,当我们

    2024年02月08日
    浏览(42)
  • 深入理解JavaScript的事件冒泡与事件捕获

    JavaScript中提供了很多操作DOM的API。 事件冒泡 和 事件捕获 是指浏览器中处理DOM元素上事件的两种不同方式。事件冒泡和事件捕获都是 JavaScript事件模型 中的一部分,可以用来 处理事件 。 对于这个问题,在实际开发中,并不是非常重要,因为在工作中我们基本上不会直接操

    2024年02月10日
    浏览(51)
  • C# 中的委托和事件机制

    在C#中,委托和事件是非常重要的概念,用于实现程序中的回调和事件处理。在这里,我们将介绍C#中委托和事件机制的基础知识和用法。 委托是一种类似于C/C++函数指针的概念,它允许将方法作为参数传递到其他方法中,以实现回调函数的功能。委托是一种类型,它可以表示

    2023年04月21日
    浏览(34)
  • 带你深入了解Android的事件分发机制

    Android的事件分发机制是指在Android系统中,如何将用户的触摸事件、按键事件等传递给正确的View进行处理的一套机制。它是Android应用程序中实现交互的重要部分,确保用户的操作能够被正确地捕获和处理。 Android的事件分发机制涉及到以下几个核心概念:事件源、事件分发、

    2024年02月16日
    浏览(33)
  • 深入理解Linux 内核追踪机制

    Linux 存在众多 tracing tools,比如 ftrace、perf,他们可用于内核的调试、提高内核的可观测性。众多的工具也意味着繁杂的概念,诸如 tracepoint、trace events、kprobe、eBPF 等,甚至让人搞不清楚他们到底是干什么的。本文尝试理清这些概念。   Probe Handler 如果我们想要追踪内核的一

    2024年02月15日
    浏览(60)
  • Java垃圾回收机制深入理解

    Java垃圾回收机制是Java虚拟机(JVM)的核心组件之一,对于内存管理起到至关重要的作用。它能自动追踪并管理应用程序中创建的对象,当这些对象不再使用时,垃圾回收机制会自动回收其占用的内存,使这部分内存能够被再次利用。此机制极大地减少了开发者需要手动管理

    2024年02月09日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包