自古以來,同步/非同步都是八股文第一章

2023-06-28 18:00:18

好久沒上線了,今天記錄程式設計中老掉牙的幾個關鍵術語,一個言簡意賅的術語定義包含主謂賓定狀補, 我們應從貌似雷同的術語中體會到不同術語的表象行為、側重點。

下面給出的3對技術術語,都是很核心、易混淆的概念點,但是多少還是有些表象、側重點的不同。

書讀百遍其義自見, 請關注最下方給出的微軟官方技術文獻, 自勉!!

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

阻塞操作不等於同步,非阻塞操作也不等於非同步。實際上,它們之間並沒有直接的聯絡。

先說同步,這個很簡單,就是按照程式碼來順序執行。

比如下面這段虛擬碼:

local res, err  = query-mysql(sql)
local value, err = query-redis(key)

在同一請求連線中,如果要等 MySQL 的查詢結果返回後,才能繼續去查詢 Redis,那就是同步;

如果不用等 MySQL 的返回,就能繼續往下走,去查詢 Redis,那就是非同步。

完全不care MYSQL的查詢結果,也不是業務想要的,一般的實踐是query-mysql函數快速返回一個awaitable物件,通過狀態查詢、事件通知的方式拿到非同步行為的結果。


再來說說非阻塞,這是一個很容易和「非同步」混淆的概念。

這裡我們說的「阻塞」,特指阻塞作業系統執行緒

我們繼續看上面的例子,假設查詢 MySQL 需要1s 的時間,如果在這1s 內,作業系統的資源(CPU)是空閒著並傻傻地等待返回,那就是阻塞;

如果 CPU 趁機去處理其他連線的請求,那就是非阻塞。

總體而言:

同步/非同步雖然表現為函數呼叫,實際宏觀上描述了一資訊對齊方式, 非同步呼叫,非同步通訊,非同步任務均表現為發出通訊動作後即刻返回,通過狀態通知、回撥函數來拿到通訊結果。

阻塞/非阻塞關注的是應用程式在等待資料返回的狀態問題:在得到結果之前,cpu若傻傻等待是阻塞(被掛起)。

.NET非同步程式設計的三種套路

  1. 基於任務的非同步模式 (TAP), 主流推薦
  2. 基於事件的非同步模式 (EAP), 過時不推薦
  3. 非同步程式設計模型 (APM) 模式(也稱為 IAsyncResult 模式), 過時不推薦

2,3已經不被推薦(2,3其實很貼近非同步的行為認知),目前主流推薦的TAP async/await語法糖,以同步姿勢簡化了非同步程式設計, 但是語法糖也讓我們不容易理解非同步的本質: async/await語法糖具備傳染性,導致async/await在整個程式碼結構氾濫使用,在被傳染的async/await層級, 根本不體現通訊互動,弱化了開發者對於最底層是非同步通訊的認知。

微軟喜歡搞拖拽控制元件、語法糖給到開發者,讓我們沉迷於便利的開發體驗,忽視了樸素的核心本質。


2. 事件/訊息

事件是對條件或狀態更改的輕量級通知。

  • 事件的釋出者對如何處理事件沒有期望。
  • 事件的使用者決定如何處理通知。
  • 事件報告狀態變化並且是可操作的, 要進行下一步,消費者只需要知道發生了什麼。事件資料包含關於發生了什麼事情的資訊,但不包含觸發事件的資料。例如,事件通知使用者檔案已建立。它可能有關於檔案的一般資訊,但它沒有檔案本身。
  • 事件可以是離散的單位,也可以是一系列事件的一部分。
    一系列事件報告了一種狀況,並且是可分析的。這些事件是按時間順序排列並相互關聯的。消費者可通過序列事件來分析發生了什麼。

訊息是由服務生成的原始資料,將在其他地方使用或儲存 。

  • 訊息包含觸發訊息管道的資料。
  • 訊息的釋出者對於消費者如何處理訊息有一個期望。雙方之間存在一份契約。
    例如,釋出者傳送帶有原始資料的訊息,並期望消費者從該資料建立檔案,並在工作完成時傳送響應。

3. 委託/事件

委託更像一個類的一個屬性,只不過屬性值是函數,公開的委託可以像類屬性一樣,自由賦值。

在眾多語言中,委託與閉包密切相關。

和委託類似,事件也是後期繫結機制。 實際上,事件是建立在對委託的語言支援之上。

 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() 語法。

使用委託還是事件有一些考量:

事件是對條件或狀態更改的輕量級通知。事件有可能被提前預置了反饋,也可能根本沒預置反饋。

(1). 若偵聽器可選,更傾向事件

A元件引發了事件,也許並不引發其他元件的連鎖反應,也就是沒有預置偵聽器,這種雖然用委託也行,但是更傾向對事件賦值偵聽器。

(2). 事件只能由定義事件的元件自行觸發 ,而不能由外部觸發。

包含事件的類以外的類只能新增和刪除事件偵聽器;只有包含事件的類才能引發事件。
還是那句話,事件更強調元件在滿足條件或自身狀態變更時觸發。

(3). 事件不care偵聽器的返回值

與(1)相關,因為事件的引發者本身也不care有沒有偵聽器。


結語

搬磚多年,越來越體會到精準理解術語的重要性,一個言簡意賅的術語定義 包含主謂賓定狀補, 我們應從貌似雷同的術語中體會到不同術語的表象行為、側重點。

上面三對概念:冥冥中存在某種微妙聯絡。

同步/非同步: 描述了資訊的對齊方式,如果是非同步會即時返回,使用狀態通知、回撥事件來獲得操作結果。

事件/訊息:描述了資訊的側重點, 事件強調了某元件在滿足某種條件、時間點而觸發了某次行為,不care是否有消費方對這個行為產生了連鎖反應。
訊息是生產方要傳遞的原始資料,訊息生產方對訊息被消費是有期待的(存在訊息格式便於消費方理解)。

委託/事件: 更接近於事件的技術實現,事件是基於委託實現的,事件更強調內生引發、委託可認為是類屬性。