網站首頁 編程語言 正文
一、 本專欄圖示概念規范
本專欄是對 異步編程 的系統探索,會通過各個方面去認知、思考 異步編程 的概念。期間會用到一些圖片進行表達與示意,在一開始先對圖中的元素和 基本概念 進行規范和說明。
1. 任務概念規范
任務 : 完成一項需求的基本單位。
分發任務: 觸發任務開始的動作。
任務結束: 任務完成的標識。
任務生命期: 任務從開始到完成的時間跨度。
如下所示,方塊 表示任務;當 箭頭指向一個任務時,表示對該任務進行分發;任何被分發的任務都會結束。在任務分發和結束之間,有一條虛線進行連接,表示 任務生命期 。
2. 任務的狀態
未完成 : Uncompleted
成功完成 : Completed with Success
異常結束 : Completed with Error
一個任務生命期間有三種狀態,如下通過三種顏色表示。在 任務結束 之前,該任務都是 未完成 態,通過 淺藍色 表示;任何被分發的任務都是為了完成某項需求,任何任務都會結束,在結束時刻根據是否完成需求,可分為 成功完成 和 異常結束 兩種狀態,如下分別用 綠色 和 紅色 表示。
3. 時刻與時間線
機體 : 任務分發者或處理者。
時刻: 機體運行中的某一瞬間。
時間線: 所有時刻構成的連續有向軸線。
在一個機體運行的過程中,時間線是絕對的,通過 紫色有向線段 表示時間的流逝的方向。時刻 是時間線上任意一點 ,通過 黑點 表示。
4.同步與異步
同步 : 機體在時間線上,將任務按順序依次分發。
同步執行任務時,前一個任務完成后,才會分發下一任務。意思是說: 任意時刻只有一個任務在生命期中。
異步: 機體在時間線上,在一個任務未完成時,分發另一任務。
也就是說通過異步編程,允許某時刻有兩個及以上的任務在生命期中。如下所示,在 任務1 完成后,分發 任務2; 在 任務2 未結束的情況下,可以分發 任務 3 。此時對于任務 3 來說,任務 2 就是異步執行的。
二、理解單線程中的異步任務
上面對基本概念進行了規范,看起來可能比較抽象,下面我們通過一個小場景來理解一下。媽媽早上出門散步,臨走前囑咐:
小捷,別睡了。快起床,把被子曬一下,地掃一下。還有,沒開水了,記得燒。
當前場景下只有小捷 一個機體,需要完成的任務有四個:起床、曬被、拖地 、燒水 。
1. 任務的分配
當機體有多個任務需要分發時,需要對任務進行分配。認識任務之間的關系,是任務分配的第一步。只有理清關系,才能合理分配任務。分配過程中需要注意:
[1] 任務之間可能存在明確的先后順序,比如起床 需要在 曬被 之前。
[2] 任務之間先后順序也可能無所謂,比如先掃地還是先曬被,并沒有太大區別。
[3] 某類任務只需要機體來分發,生命期中不需要機體處理,并且和后續的任務沒有什么關聯性,比如燒水任務。
像燒水這種任務,即耗時,又不需要機體在任務生命期中做什么事。如果這類任務使用同步處理,那么任務期間機體能做的事只有 等待 。對于一個機體來說,這種等待就會意味著阻塞,不能處理任何事。
結合日常生活,我們知道當前場景之中,想要發揮機體最大的效力,最好的方式是起床之后,先分發 燒水任務,不需要等待燒水任務完成,就去執行曬被、掃地任務。這樣的任務分配就是將 燒水 作為一個異步任務來執行的。
但在如果在分配時,將燒水作為最后一個任務,那么異步執行的價值就會消失。所以對任務的合理分配,對機體的處理效率是非常重要的。
2.異步任務特點
從上面可以看出,異步任務 有很明顯的特征,并不是任何任務都有必要異步執行。特別是對于單一機體來說,任務生命期間需要機體親自參與,是無法異步處理的。?比如一個人不能一邊曬被 ,一邊 掃地 。所以對于單線程來說,像一些只需要 分發任務,任務的具體執行邏輯由其他機體完成的任務,適合使用 異步 處理,來避免不必要的等待。
這種任務,在應用程序中最常見的是網絡 io和 磁盤 io 的任務。比如,從一個網絡接口中獲取數據,對于機體來說,只需要分發任務來發送請求,就像燒水時只需要裝水按下啟動鍵一樣。而服務器如何根據請求,查詢數據庫來返回響應信息,數據如何在網絡中傳輸的,和分發任務的機體沒有關系。磁盤的訪問也是一樣,分發讀寫文件任務后,真正干活的是操作系統。
像這類任務通過異步處理,可以避免在分發任務后,機體因等待任務的結束而阻塞。在等待其他機體處理的過程中,去分發其他任務,可以更好地分配時間。比如下面所示,網絡數據獲取 的任務分發后,需要通過網絡把請求傳輸給服務器,服務器進行處理,給出響應結果。
整個任務處理的過程,并不需要機體參與,所以分發 網絡數據獲取 任務后,無需等待任務完成,接著分發 構建加載中界面 的任務,來展示加載中的界面。從而給出用戶交互的反饋,而不是阻塞在那里等待網絡任務完成,這就是一個非常典型的異步任務使用場景。
3. 異步任務完成與回調
前面的介紹中可以看出,異步任務在分發之后,并不會等待任務完成,在任務生命期中,可以繼續分發其他任務。但任何任務都會結束,很多時候我們需要知道異步任務何時完成,以及任務的完成情況、任務返回的結果,以便該任務后續的處理。比如,在燒水完成之后,我們需要處理 沖水 的任務。
這就要涉及到一個對異步而言非常重要的概念:
回調: 任務在生命期間向機體提供通知的方式。
比如 燒水 任務完成后,燒水壺 “叮” 的一聲通知任務完成;或者燒水期間發生故障,發出報警提示。這種在任務生命期間向機體發送通知的方式稱為回調 。在編程中,回調一般是通過 函數參數 來實現的,所以習慣稱 回調函數 。 另外,函數可以傳遞數據,所以通過回調函數不僅可以知道任務結束的契機,還可以通過回調參數將任務的內部數據暴露給機體。
比如在實際開發中,分發 網絡數據獲取 的任務,其目的是為了通過網絡接口獲取數據。就像燒開水任務完成之后,需要把 開水 倒入瓶中一樣。我們也需要知道 網絡數據獲取 的任務完成的時機,將獲取的數據 "倒入" 界面中進行顯示。
從發送異步任務,到異步任務結束的回調觸發,就是一個異步任務完整的 生命期。
三、 Dart 語言中的異步
上面只是介紹了 異步模型 中的概念,這些概念是共通的,無論什么編程語言都一樣適用。就像現實中,無論使用哪國的語言表述,四則運算的概念都不會有任何區別。只是在表述過程中,表現形式會在語言的語法上有所差異。
1.編程語言中與異步模型的對應關系
每種語言的描述,都是對概念模型的具象化實現。這里既然是對 Flutter 中異步編程的介紹,自然要說一下 Dart 語言對異步模型的描述。
對于 任務 概念來說,在編程中和 函數 有著千絲萬縷的聯系:函數體 可以實現 任務處理的具體邏輯,也可以觸發 任務分發的動作 。但我并不認為兩者是等價的, 任務 有著明確的 目的性 ,而 函數 是實現這種 目的 的手段。在編程活動中,函數 作為 任務 在代碼中的邏輯體現,任務 應先于 函數 存在。
如下代碼所示,在 main 函數中,觸發 calculate 任務,計算 0 ~ count 累加值和計算耗時,并返回。其中 calculate 函數就是對該任務的代碼實現:
void main(){
TaskResult result = calculate();
}
TaskResult calculate({int count = 10000000}){
int startTime = DateTime.now().millisecondsSinceEpoch;
int result = loopAdd(count);
int cost = DateTime.now().millisecondsSinceEpoch-startTime;
return TaskResult(
cost:cost,
data:result,
taskName: "calculate"
);
}
int loopAdd(int count) {
int sum = 0;
for (int i = 0; i <= count; i++) {
sum+=i;
}
return sum;
}
這里 TaskResult 類用于記錄任務完成的信息:
class TaskResult {
final int cost;
final String taskName;
final dynamic data;
TaskResult({
required this.cost,
required this.data,
required this.taskName,
});
Map<String,dynamic> toJson()=>{
"taskName":taskName,
"cost":cost,
"data": data
};
}
2.Dart 編程中的異步任務
如下在計算之后,還有兩個任務:saveToFile 任務,將運算結果保存到文件中;以及 render 任務將運算結果渲染到界面上。
void main() {
TaskResult result = cacaulate();
saveToFile(result);
render(result);
}
這里 render 任務暫時通過在控制臺打印顯示作為渲染,邏輯如下:
void render(TaskResult result) {
print("結果渲染: ${result.toJson()}");
}
下面是將結果寫入文件的任務實現邏輯。其中 File 對象的 writeAsString 是一個異步方法,可以將內容寫入到文件中。通過 then 方法設置回調,監聽任務完成的時機。
void saveToFile(TaskResult result) {
String filePath = path.join(Directory.current.path, "out.json");
File file = File(filePath);
String content = json.encode(result);
file.writeAsString(content).then((File value){
print("寫入文件成功:!${value.path}");
});
}
3.當前任務分析
如下是這三個任務的執行示意,在 saveToFile 中使用 writeAsString 方法將異步處理寫入邏輯。
這樣就像在燒水任務分發后,可以執行曬被一樣。saveToFile 任務分發之后,不需要等待文件寫入完成,可以繼續執行 render 方法。日志輸出如下:渲染任務的執行并不會因寫入文件任務而阻塞,這就是異步處理的價值。
四、異步模型的延伸
1. 單線程異步模型的局限性
本文主要介紹 異步模型 的概念,認識異步的作用,以及 Dart 編程語言中異步方法的基本使用。至于代碼中更具體的異步使用方式,將在后期文章中結合詳細介紹。另外,一般情況下,Dart 是以 單線程 運行的,所以本文中強調的是 單線程 下的異步模型。
仔細思考一下,可以看出,單線程中實現異步是有局限性的。比如說需要解析一個很大的 json ,或者進行復雜的邏輯運算等 耗時任務,這種必須由 本機體 處理的邏輯,而不是 等待結果 的場景,是無法在單線程中異步處理的。
就像是 掃地 和 曬被 任務,對于單一機體來說,不可能同時參與到兩個任務之中。在實際開發中這兩個任務可類比為 解析超大 json 和 顯示解析中界面 兩個任務。如果前者耗時三秒,由于單線程 中同步方法的阻塞,界面就會卡住三秒,這就是單線程異步模型的 局限性。
2. 多線程與異步的關系
上面問題的本質矛盾是:一個機體無法 同時 參與到兩件任務 具體執行過程中。解決方案也非常簡單,一個人搞不定,就搖人唄。多個機體參與任務分配的場景,就是 多線程 。 很多人都會討論 異步 和 多線程 的關系,其實很簡單:兩個機體,一個 掃地,一個 曬被,同一時刻,存在兩個及以上的任務在生命期中,一定是異步的。毫無疑問,多線程 是 異步模型 的一種實現方式。
3. Dart 中如何解決單線程異步模型的局限性
像 C++ 、Java 這些語言有 多線程 的支持,通過 “搖人” 可以充分調度 CPU 核心,來處理一些計算密集型的任務,實現任務在時間上的最合理分配。
絕大多數人可能覺得 Dart 是一個單線程的編程語言,其實不然。可能是很多人并沒有在 Flutter 端做過計算密集型的任務,沒有對多線程迫切的需要。畢竟 移動/桌面客戶端 大多是網絡、數據庫訪問等 io 密集型 的任務,人手一個終端,沒有什么高并發的場景。不像后端那樣需要保證一個終端被百萬人同時訪問。
或者計算密集型的任務都有由平臺機體進行處理,將結果通知給 Flutter 端。這導致 Dart 看起來更像是一個 任務分發者,發號施令的人,絕大多數時候并不需要親自參與任務的執行過程中。而這正是單線程下的異步模型所擅長的:借他人之力,監聽回調信息。
其實我們在日常開發中,使用的平臺相關的插件,其中的方法基本上都是異步的,本質上就是這個原因。平臺 是個燒水壺,燒水任務只需要分發 和 監聽回調。至于水怎么燒開,是 平臺 需要關心的,這和 網絡 io 、磁盤 io 是很類似的,都是 請求 與 響應 的模式。這種任務,由單線程的異步模型進行處理,是最有效的,畢竟 “搖人” 還是要管飯的。
那如果非要在 Dart 中處理計算密集型的任務,該如何是好呢?不用擔心,Dart 的 isolate 機制可以完成這項需求。關于這點,在后面會進行詳述。認識 異步 是什么,是本文的核心,那本文就到這里,謝謝觀看 ~
原文鏈接:https://juejin.cn/post/7144878072641585166
相關推薦
- 2022-07-01 使用Python讀寫多個sheet文件_python
- 2022-12-06 深入了解C語言中的字符串和內存函數_C 語言
- 2022-06-08 VM配置Centos7虛擬機
- 2022-03-28 Python實現網頁文件轉PDF文件和PNG圖片的示例代碼_python
- 2022-06-18 Android自定義雙向滑動控件_Android
- 2022-12-05 一文深入了解Python中的繼承知識點_python
- 2023-04-21 C語言哈希表概念超詳細講解_C 語言
- 2022-06-14 jquery實現點擊按鈕顯示與隱藏效果_jquery
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支