自古以来,同步/异步都是八股文第一章

好久没上线了,今天记录编程中老掉牙的几个关键术语,一个言简意赅的术语定义包含主谓宾定状补, 我们应从貌似雷同的术语中体会到不同术语的表象行为、侧重点。

下面给出的3对技术术语,都是很核心、易混淆的概念点,但是多少还是有些表象、侧重点的不同。

书读百遍其义自见, 请关注最下方给出的微软官方技术文献, 自勉!!

阻塞操作不等于同步,非阻塞操作也不等于异步。实际上,它们之间并没有直接的联系。

1. 同步/异步、 阻塞/非阻塞

异步是指程序具备同时执行多个任务的能力(无需等待一个任务完成再开启下一个任务)
(Asynchronous refers to the ability of a program or system to perform multiple tasks simultaneously without waiting for each task to be complete before starting the next one.)

[微软官方给出了一个做早餐的例子(https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/),做早餐有很多工序,但是每道工序相互不影响,你开启一道工序A之后,可以转去做另外一道工序B, 无需等待工序A完成再开启工序B。

同步就很好理解了,程序或者系统不具备同时执行多个任务的能力, 必须等到一个任务执行完,才能开启新的任务。

从这个定义看,同步/异步 更像是在大局观层面定义了程序是否具备能连续起任务的能力


非阻塞是指程序不会被特定任务阻塞或挂起,cpu能继续工作的能力 (Non-blocking, on the other hand, refers to the ability of a program or system to continue execution without being blocked or held up by a specific task)。

非阻塞更像是在局部定义了: 即便程序当前任务某种程度上受限于某任务,程序当前任务依然可以继续执行 (受限可以通过检查任务状态来缓解)。

https://www.geeksforgeeks.org/difference-between-asynchronous-and-non-blocking/


.NET异步编程的三种套路

  1. 基于任务的异步模式 (TAP), 主流推荐
  2. 基于事件的异步模式 (EAP), 过时不推荐
  3. 异步编程模型 (APM) 模式(也称为 IAsyncResult 模式), 过时不推荐

2,3已经不被推荐(2,3其实很贴近异步的行为认知),目前主流推荐的TAP async/await语法糖,以同步姿势简化了异步编程, 但是语法糖也让我们不容易理解异步的本质: async/await语法糖具备传染性,导致async/await在整个代码结构泛滥使用,在被传染的async/await层级, 根本不体现通信交互,弱化了开发者对于最底层是异步通信的认知。

微软喜欢搞拖拽控件、语法糖给到开发者,让我们沉迷于便利的开发体验,忽视了朴素的核心本质。


2. 事件/消息

事件是对条件或状态更改的轻量级通知。

  • 事件的发布者对如何处理事件没有期望。
  • 事件的使用者决定如何处理通知。
  • 事件报告状态变化并且是可操作的, 要进行下一步,消费者只需要知道发生了什么。事件数据包含关于发生了什么事情的信息,但不包含触发事件的数据。例如,事件通知使用者文件已创建。它可能有关于文件的一般信息,但它没有文件本身。
  • 事件可以是离散的单位,也可以是一系列事件的一部分。
    一系列事件报告了一种状况,并且是可分析的。这些事件是按时间顺序排列并相互关联的。消费者可通过序列事件来分析发生了什么。

消息是由服务生成的原始数据,将在其他地方使用或存储 。

  • 消息包含触发消息管道的数据。
  • 消息的发布者对于消费者如何处理消息有一个期望。双方之间存在一份契约。
    例如,发布者发送带有原始数据的消息,并期望消费者从该数据创建文件,并在工作完成时发送响应。

3. 委托/事件

委托更像类的一个属性,只不过属性值是函数,公开的委托可以像类属性一样,自由赋值。

实际上,C#事件是基于委托实现。

 In the .NET class library, events are based on the EventHandler delegate and the EventArgs base class.  
public delegate void EventHandler(object? sender, EventArgs e);
 后期绑定机制: 组件通过调用可在运行时识别的方法进行通信。 它们都支持单个和多个订阅服务器方法。 这称为单播和多播支持。  

两者均支持用于添加和删除处理程序的类似语法,引发事件和调用委托也是相同的调用语法。 它们甚至都支持与 ?. 运算符结合的 Invoke() 语法 (也有可能是调用BeginInvoke开启异步任务)。

使用委托还是事件有一些考量:

事件是对条件或状态更改的轻量级通知。事件有可能被提前预置了反馈,也可能根本没预置反馈。

(1). 若侦听器可选,更倾向事件

A组件引发了事件,也许并不引发其他组件的连锁反应,也就是没有预置侦听器,这种虽然用委托也行,但是更倾向对事件赋值侦听器。

(2). 事件只能由定义事件的组件自行触发 ,而不能由外部触发。

包含事件的类以外的类只能添加和删除事件侦听器;只有包含事件的类才能引发事件。
还是那句话,事件更强调组件在满足条件或自身状态变更时触发。

(3). 事件不care侦听器的返回值

与(1)相关,因为事件的引发者本身也不care有没有侦听器。


结语

搬砖多年,越来越体会到精准理解术语的重要性,一个言简意赅的术语定义 包含主谓宾定状补, 我们应从貌似雷同的术语中体会到不同术语的表象行为、侧重点。

上面三对概念:冥冥中存在某种微妙联系。

同步/异步: 描述了信息的对齐方式,如果是异步会即时返回,使用状态通知、回调事件来获得操作结果。

事件/消息:描述了信息的侧重点, 事件强调了某组件在满足某种条件、时间点而触发了某次行为,不care是否有消费方对这个行为产生了连锁反应。
消息是生产方要传递的原始数据,消息生产方对消息被消费是有期待的(存在消息格式便于消费方理解)。

委托/事件: 更接近于事件的技术实现,事件是基于委托实现的,事件更强调内生引发、委托可认为是类属性。

  • 流程B:发布事件
  • 流程A:订阅事件, 注册回调函数
  • 流程B: 满足时机或者条件,触发事件

我们回顾winform编程中,开发者眼里只有注册事件、处理事件,貌似没有体现"满足流程B的时机” 这个行为。

Button btn = new Button();
btn.Parent = this;
btn.Text = "Hit Me";
btn.Location = new Point(100,100);

//Event handler is assigned to the button click event
btn.Click += new EventHandler(onClcik);

//call when button clicked
public void onClcik(object sender, EventArgs e)
{
      MessageBox.Show("You clicked me");
}

实际是因为win32 给我们隐藏了消息循环这个核心的机制。

MSG msg;
 
while(GetMessage(&msg,NULL,0,0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Windows为每个Windows程序都维护了一个消息队列,当鼠标、键盘事件被触发后,相应的鼠标/键盘驱动程序就会把这些事件转换成相应的消息(也就是上面代码中的MSG结构),
在这个消息里包含有一些信息,比如鼠标点击的点啊,消息的类型啊等等。
而上面的while循环中的GetMessage方法就是不断的从这个消息队列里取消息出来,然后处理,这样窗体就能响应用户的输入了。

所以我们的注册函数的行为,其实是给消息准备属性。

posted @ 2023-06-28 16:45  博客猿马甲哥  阅读(401)  评论(0编辑  收藏  举报