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

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

下面给出的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能继续工作的能力, 着力点更贴近单方。

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

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异步编程的三种套路

    1. 基于任务的异步模式 (TAP)
    1. 基于事件的异步模式 (EAP)
    1. 异步编程模型 (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;
posted @ 2023-06-28 16:45  码甲哥不卷  阅读(432)  评论(0编辑  收藏  举报