自古以来,同步/异步都是八股文第一章
今天记录编程中老掉牙的几个关键术语,一个言简意赅的术语定义包含主谓宾定状补, 我们应从貌似雷同的术语中体会到不同术语的表象行为、侧重点。
下面给出的3对技术术语,都是很核心、易混淆的概念点,但是多少还是有些表象、侧重点的不同。
阻塞操作不等于同步,非阻塞操作也不等于异步。实际上,它们之间并没有直接的联系。
1. 同步/异步、 阻塞/非阻塞
1.1 同步
大部分编程语言中对方法的调用是同步执行的, 在单一线程执行体内,方法的调用就是要拿到结果才会返回, 遇到长时间的IO操作也需要等待完成才能返回。
现在同步/异步已经不局限于方法调用和IO操作, 现在也外延到宏观的通信方式。
1.2 异步
异步是指程序无需等待IO操作完成即可继续执行原线程任务,宏观上“无需等待一个任务完成即可继续执行/开启新任务”。
(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.)
微软早餐: 做早餐有很多工序,但每道工序相互不影响,你开启一道工序A之后,可以转去做另外一道工序B, 无需等待工序A完成再开启工序B。
1.3 阻塞、非阻塞
阻塞、非阻塞更强调程序受限于某任务,程序当前cpu能继续工作的能力, 着力点更贴近单方。
2. 同步、异步的实现
在微服务体系下,不同服务之间的调用有哪些常见的方式?除了gRPC直接调用,是否还有其他异步的方式?
在微服务架构中,服务之间的调用主要有同步和异步两种方式。
同步调用最常见的是通过HTTP/RESTful API或者gRPC来进行。
异步调用则通常使用消息队列(如RabbitMQ、Kafka)来实现,这种方式的好处是解耦了服务之间的调用,提高了系统的可扩展性和可靠性。
另外,事件驱动架构和发布订阅模式也是异步调用的典型例子。
2.1 事件驱动架构怎么体现异步?
知乎上有个问题 : 为什么几乎所有的GUI界面都采用事件驱动编程模型
因为一般GUI的用户输入频率比较低、事件源较分散,如果每个控件不停做轮询去获取用户界面事件,浪费时间又可能比较难处理 (想象两个事件同时发生,处理A事件的同时也去处理B事件,界面变形)。
而事件处理则是由中央的系统把输入事件派送给需要的部分。
Button btn = new Button();
btn.Parent = this;
btn.Text = "Hit Me";
btn.Location = new Point(100,100);
//Button 公开了一个点击事件, 此处被绑定了事件处理器
btn.Click += new EventHandler(onClcik);
//call when button clicked
public void onClcik(object sender, EventArgs e)
{
MessageBox.Show("You clicked me");
}
事件循环是事件架构的核心机制:
Windows为每个Windows程序都维护了一个消息队列,当鼠标、键盘事件被触发后,相应的鼠标/键盘驱动程序就会把这些事件转换成相应的消息;
消息message里面包含一些信息information(消息源: 控件句柄、消息类型: 点击);
事件的while循环将消息被派发到控件。
MSG msg;
while(GetMessage(&msg,NULL,0,0)) // 消息分发器
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
protected void DispathMessage(*MSG msg)
{
switch(msg.Handle)
{
case "btn1": msg.Handle.WinProc(msg)
}
}
// 控件上存在WinProc窗口过程函数
protected override void WndProc(ref Msg m)
{
const int WM_LBUTTONDOWN = 0x0201;
// 检查消息类型
if (m.Msg == WM_LBUTTONDOWN)
{
onClcik(msg.Args);
}
// 调用基类的 WndProc 方法以进行默认处理
base.WndProc(ref m);
}
从通信的角度, 收到的用户点击事件并不是被即时处理,而是只接收进事件队列,主线程按照节奏执行自己的事件循环。
redis nginx等这些单线程事件驱动架构,核心是单线程事件循环+ io多路复用技术,因此需要开发者控制大key的产生、控制单个请求的处理时长。
.NET底层Kestrel 服务器是一个事件驱动的服务器,核心是基于线程池+ 异步io技术。
3. .NET异步编程的三种套路
-
- 基于任务的异步模式 (TAP)
-
- 基于事件的异步模式 (EAP)
-
- 异步编程模型 (APM) 模式(也称为 IAsyncResult 模式)
2,3已经不被推荐(2,3其实很贴近异步的行为认知),目前主流推荐的TAP async/await语法糖,
- 开发者使用层面: 以同步姿势简化了异步编程, 但是语法糖也让我们不容易理解异步的本质:
- 实质: 利用状态机模型 将异步任务划分为几个状态,在异步完成阶段由底层的事件循环机制,由线程池IO线程去执行回调, 更新Task状态到RunToCompletion,最后默认的任务调度器调度后继任务执行。
- 特征 : async/await语法糖具备传染性,导致async/await在整个代码结构泛滥使用,在被传染的async/await层级, 根本不体现通信交互,弱化了开发者对于最底层是异步通信的认知。
4. 事件/消息
事件是对条件或状态更改的轻量级通知。
- 事件的发布者对如何处理事件没有期望。
- 事件的使用者决定如何处理通知。
- 事件报告状态变化并且是可操作的, 要进行下一步,消费者只需要知道发生了什么。事件数据包含关于发生了什么事情的信息,但不包含触发事件的数据。例如,事件通知使用者文件已创建。它可能有关于文件的一般信息,但它没有文件本身。
- 事件可以是离散的单位,也可以是一系列事件的一部分。
一系列事件报告了一种状况,并且是可分析的。这些事件是按时间顺序排列并相互关联的。消费者可通过序列事件来分析发生了什么。
消息是由服务生成的原始数据,将在其他地方使用或存储 。
- 消息包含触发消息管道的数据。
- 消息的发布者对于消费者如何处理消息有一个期望。双方之间存在一份契约。
例如,发布者发送带有原始数据的消息,并期望消费者从该数据创建文件,并在工作完成时发送响应。
5. C# delegate/event
委托更像类的一个属性,只不过属性值是函数,公开的委托可以像类属性一样,自由赋值。
public delegate void EventHandler(object sender, EventArgs e);
C# event基于委托实现, 虽然使用 event 定义事件时常与 EventHandler委托一起使用,但实际上,event 可以与任何符合委托签名的类型一起使用,并不局限于 EventHandler。
Q: 既然如此,event还有必要吗?
A: event 能提供更高级别的抽象和安全性、 更清晰的语义。
- 限制直接调用:使用 event 关键字时,事件只能在定义它的类或结构体中触发,这意味着外部代码无法直接调用事件,必须通过提供的触发机制(如方法)来触发,这种限制防止了事件被意外或恶意触发。
- 访问控制:event 提供了一种封装机制,限制了对委托实例的直接访问。虽然委托本身是一个多播委托,可以直接调用和修改,但使用 event 后,外部类只能通过 += 和 -= 操作符来订阅或取消订阅事件,而不能直接赋值或清除所有订阅者。
使用委托还是事件有一些考量:
从语义上: 事件是对条件或状态更改的轻量级通知,事件有可能被提前预置了反馈侦听器,也可能根本没预置反馈侦听器。
结语
搬砖多年,越来越体会到精准理解术语的重要性,一个言简意赅的术语定义 包含主谓宾定状补, 我们应从貌似雷同的术语中体会到不同术语的表象行为、侧重点。
上面三对概念:冥冥中存在某种微妙联系。
同步/异步: 描述了信息的通信对齐方式,如果是异步会即时返回,使用状态通知、回调事件来获得操作结果。
事件/消息:描述了信息的侧重点, 事件强调了某组件在满足某种条件、时间点而触发了某次行为,不care是否有消费方对这个行为产生了连锁反应。
消息是生产方要传递的原始数据,消息生产方对消息被消费是有期待的(存在消息格式便于消费方理解)。
委托/事件: 更接近于事件的技术实现,事件是基于委托实现的,事件更强调内生引发、委托可认为是类属性。
- 流程B:发布事件
- 流程A:订阅事件, 注册回调函数
- 流程B: 满足时机或者条件,触发事件
看一个传统的猫和老鼠的实例,下面是一个发布订阅模式的异步实例。
public class Cat
{
public Cat(string name)
{
this.Name = name;
}
public Action OnBarking;
private string Name { get; }
public void Barking()
{
Console.WriteLine($"{DateTime.Now} {Name} is barking ...");
if (OnBarking != null)
{
OnBarking();
}
}
}
public class Mouse
{
public Mouse(string name)
{
Name = name;
SeekFoodAndEat();
}
private string Name { get; }
private bool _isCatBarked;
private void SeekFoodAndEat()
{
Task.Run(() =>
{
while (_isCatBarked == false) // 闭包
{
Console.WriteLine($"{DateTime.Now} {Name} is start seek food and eat.");
}
Console.WriteLine($"{DateTime.Now} Cat barked,{Name} stop seeking food ");
});
}
public void OnCatBarked()
{
_isCatBarked = true;
}
}
var cat = new Cat("加菲猫");
var mouse1 = new Mouse("m1");
var mouse2 = new Mouse("m2");
cat.OnBarking += mouse1.OnCatBarked;
cat.OnBarking += mouse2.OnCatBarked;
本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/17511865.html
欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化![](https://blog-static.cnblogs.com/files/JulianHuang/QR.gif)