赞
踩
目录
都知道事件的本质是一个多播委托(MulticastDelegate),但对于事件的机制和用法一直懵懵懂懂,本篇主要对此进行深入分析,首先要明确关于事件的疑惑:
Event 是同步还是异步执行的?(答:同步执行)
如果是多个订阅,事件执行的顺序是什么?(答:串行执行)
如果事件执行中发生异常,会发生什么事情?(答:如果一个订阅者(事件)发生异常。未执行的事件不会继续执行)
事件支持异步执行吗?(答:支持)
事件触发后,跨进程可以触发到吗?(答:可以)
事件作为类的成员,一般是通过事件向其他类或对象通知发生的相关事情。 发送事件的类称为发布者,接收事件的类称为订阅者。
发布者确定何时引发事件;订阅者确定对事件作出何种响应
一个事件可以有多个订阅者。 订阅者可以处理来自多个发行者的多个事件。
没有订阅者的事件永远也不会引发。
事件通常用于表示用户操作,例如单击按钮或图形用户界面中的菜单选项。
当事件具有多个订阅者时,引发该事件时会同步调用事件处理程序。 也可通过async/await达到异步调用事件的作用。
在 .NET 类库中,事件基于 EventHandler 委托和 EventArgs 基类。
定义一个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();
- }
输出结果:
可以看到,事件是一次同步执行的(执行过程也会阻塞主线程)。
在上面代码基础上,增加异步方法然后订阅:
结果输出:
可以看的,新增加的异步事件处理,的确是第一个被触发的,只不过它没有阻塞主线程处理。
小知识点:
- 在异步编程中虽然不推崇定义一个类似的
async void xxxx(){}
函数,因为这样的函数无法被主程序捕获结果或异常。 但凡是总有例外,而这个异步事件处理恰恰就是这个函数的最佳使用场景。- 上述代码是非UI编程,有关UI处理(按钮点击事件等),机制并不一样,UI为它的异步事件提供了一个SynchronizationContext,使它们能够在UI线程上恢复。它从不“等待”事件。
虽然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;
- }
- }
订阅了两个异步事件,两个同步事件,结果如下:
我们知道,在事件执行过程中,如果某个事件发生异常,就会终止未执行的事件:
这里的原因是:
在基本
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;
- }
- }
最终输出结果:
可以看到的,这里的实现剔除了短路行为,即使你的某个处理函数有异常,它依然可以向下分发事件。
通过分析需求,可以明确通过线程模拟相机实时抓拍车牌(发布者),当抓拍到车牌说明来车了,即触发一下事件:收费员收费,闸门放行(订阅者),具体代码如下:
- 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();
- }
-
- }
结果输出:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。