<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                在上一篇文章中,我帶你一起學習了如何在 Flutter 中實現動畫。對于組件動畫,Flutter 將動畫的狀態與渲染進行了分離,因此我們需要使用動畫曲線生成器 Animation、動畫狀態控制器 AnimationController 與動畫進度監聽器一起配合完成動畫更新;而對于跨頁面動畫,Flutter 提供了 Hero 組件,可以實現共享元素變換的頁面切換效果。 在之前的章節里,我們介紹了很多 Flutter 框架出色的渲染和交互能力。支撐起這些復雜的能力背后,實際上是基于單線程模型的 Dart。那么,與原生 Android 和 iOS 的多線程機制相比,單線程的 Dart 如何從語言設計層面和代碼運行機制上保證 Flutter UI 的流暢性呢? 因此今天,我會通過幾個小例子,循序漸進地向你介紹 Dart 語言的 Event Loop 處理機制、異步處理和并發編程的原理和使用方法,從語言設計和實踐層面理解 Dart 單線程模型下的代碼運行本質,從而懂得后續如何在工作中使用 Future 與 Isolate,優化我們的項目。 ## Event Loop 機制 首先,我們需要建立這樣一個概念,那就是**Dart 是單線程的**。那單線程意味著什么呢?這意味著 Dart 代碼是有序的,按照在 main 函數出現的次序一個接一個地執行,不會被其他代碼中斷。另外,作為支持 Flutter 這個 UI 框架的關鍵技術,Dart 當然也支持異步。需要注意的是,**單線程和異步并不沖突。** 那為什么單線程也可以異步? 這里有一個大前提,那就是我們的 App 絕大多數時間都在等待。比如,等用戶點擊、等網絡請求返回、等文件 IO 結果,等等。而這些等待行為并不是阻塞的。比如說,網絡請求,Socket 本身提供了 select 模型可以異步查詢;而文件 IO,操作系統也提供了基于事件的回調機制。 所以,基于這些特點,單線程模型可以在等待的過程中做別的事情,等真正需要響應結果了,再去做對應的處理。因為等待過程并不是阻塞的,所以給我們的感覺就像是同時在做多件事情一樣。但其實始終只有一個線程在處理你的事情。 等待這個行為是通過 Event Loop 驅動的。事件隊列 Event Queue 會把其他平行世界(比如 Socket)完成的,需要主線程響應的事件放入其中。像其他語言一樣,Dart 也有一個巨大的事件循環,在不斷的輪詢事件隊列,取出事件(比如,鍵盤事件、I\\O 事件、網絡事件等),在主線程同步執行其回調函數,如下圖所示: :-: ![](https://img.kancloud.cn/0c/b6/0cb6e6d34295cef460e48d139bc944ec_880x582.png) 圖 1 簡化版 Event Loop ## 異步任務 事實上,圖 1 的 Event Loop 示意圖只是一個簡化版。在 Dart 中,實際上有兩個隊列,一個事件隊列(Event Queue),另一個則是微任務隊列(Microtask Queue)。在每一次事件循環中,Dart 總是先去第一個微任務隊列中查詢是否有可執行的任務,如果沒有,才會處理后續的事件隊列的流程。 所以,Event Loop 完整版的流程圖,應該如下所示: :-: ![](https://img.kancloud.cn/70/dc/70dc4e1c222ddfaee8aa06df85c22bbc_902x678.png) 圖 2 Microtask Queue 與 Event Queue 接下來,我們分別看一下這兩個隊列的特點和使用場景吧。 首先,我們看看微任務隊列。微任務顧名思義,表示一個短時間內就會完成的異步任務。從上面的流程圖可以看到,微任務隊列在事件循環中的優先級是最高的,只要隊列中還有任務,就可以一直霸占著事件循環。 微任務是由 scheduleMicroTask 建立的。如下所示,這段代碼會在下一個事件循環中輸出一段字符串: ~~~ scheduleMicrotask(() => print('This is a microtask')); ~~~ 不過,一般的異步任務通常也很少必須要在事件隊列前完成,所以也不需要太高的優先級,因此我們通常很少會直接用到微任務隊列,就連 Flutter 內部,也只有 7 處用到了而已(比如,手勢識別、文本輸入、滾動視圖、保存頁面效果等需要高優執行任務的場景)。 異步任務我們用的最多的還是優先級更低的 Event Queue。比如,I/O、繪制、定時器這些異步事件,都是通過事件隊列驅動主線程執行的。 **Dart 為 Event Queue 的任務建立提供了一層封裝,叫作 Future**。從名字上也很容易理解,它表示一個在未來時間才會完成的任務。 把一個函數體放入 Future,就完成了從同步任務到異步任務的包裝。Future 還提供了鏈式調用的能力,可以在異步任務執行完畢后依次執行鏈路上的其他函數體。 接下來,我們看一個具體的代碼示例:分別聲明兩個異步任務,在下一個事件循環中輸出一段字符串。其中第二個任務執行完畢之后,還會繼續輸出另外兩段字符串: ~~~ Future(() => print('Running in Future 1'));// 下一個事件循環輸出字符串 Future(() => print(‘Running in Future 2')) .then((_) => print('and then 1')) .then((_) => print('and then 2’));// 上一個事件循環結束后,連續輸出三段字符串 ~~~ 當然,這兩個 Future 異步任務的執行優先級比微任務的優先級要低。 正常情況下,一個 Future 異步任務的執行是相對簡單的:在我們聲明一個 Future 時,Dart 會將異步任務的函數執行體放入事件隊列,然后立即返回,后續的代碼繼續同步執行。而當同步執行的代碼執行完畢后,事件隊列會按照加入事件隊列的順序(即聲明順序),依次取出事件,最后同步執行 Future 的函數體及后續的 then。 這意味著,**then 與 Future 函數體共用一個事件循環**。而如果 Future 有多個 then,它們也會按照鏈式調用的先后順序同步執行,同樣也會共用一個事件循環。 如果 Future 執行體已經執行完畢了,但你又拿著這個 Future 的引用,往里面加了一個 then 方法體,這時 Dart 會如何處理呢?面對這種情況,Dart 會將后續加入的 then 方法體放入微任務隊列,盡快執行。 下面的代碼演示了 Future 的執行規則,即,先加入事件隊列,或者先聲明的任務先執行;then 在 Future 結束后立即執行。 * 在第一個例子中,由于 f1 比 f2 先聲明,因此會被先加入事件隊列,所以 f1 比 f2 先執行; * 在第二個例子中,由于 Future 函數體與 then 共用一個事件循環,因此 f3 執行后會立刻同步執行 then 3; * 最后一個例子中,Future 函數體是 null,這意味著它不需要也沒有事件循環,因此后續的 then 也無法與它共享。在這種場景下,Dart 會把后續的 then 放入微任務隊列,在下一次事件循環中執行。 ~~~ //f1 比 f2 先執行 Future(() => print('f1')); Future(() => print('f2')); //f3 執行后會立刻同步執行 then 3 Future(() => print('f3')).then((_) => print('then 3')); //then 4 會加入微任務隊列,盡快執行 Future(() => null).then((_) => print('then 4')); ~~~ 說了這么多規則,可能大家并沒有完全記住。那我們通過一個綜合案例,來把之前介紹的各個執行規則都串起來,再集中學習一下。 在下面的例子中,我們依次聲明了若干個異步任務 Future,以及微任務。在其中的一些 Future 內部,我們又內嵌了 Future 與 microtask 的聲明: ~~~ Future(() => print('f1'));// 聲明一個匿名 Future Future fx = Future(() => null);// 聲明 Future fx,其執行體為 null // 聲明一個匿名 Future,并注冊了兩個 then。在第一個 then 回調里啟動了一個微任務 Future(() => print('f2')).then((_) { print('f3'); scheduleMicrotask(() => print('f4')); }).then((_) => print('f5')); // 聲明了一個匿名 Future,并注冊了兩個 then。第一個 then 是一個 Future Future(() => print('f6')) .then((_) => Future(() => print('f7'))) .then((_) => print('f8')); // 聲明了一個匿名 Future Future(() => print('f9')); // 往執行體為 null 的 fx 注冊了了一個 then fx.then((_) => print('f10')); // 啟動一個微任務 scheduleMicrotask(() => print('f11')); print('f12'); ~~~ 運行一下,上述各個異步任務會依次打印其內部執行結果: ~~~ f12 f11 f1 f10 f2 f3 f5 f4 f6 f9 f7 f8 ~~~ 看到這兒,你可能已經懵了。別急,我們先來看一下這段代碼執行過程中,Event Queue 與 Microtask Queue 中的變化情況,依次分析一下它們的執行順序為什么會是這樣的: :-: ![](https://img.kancloud.cn/8a/11/8a1106a01613fa999a35911fc5922e8b_800x540.gif) 圖 3 Event Queue 與 Microtask Queue 變化示例 * 因為其他語句都是異步任務,所以先打印 f12。 * 剩下的異步任務中,微任務隊列優先級最高,因此隨后打印 f11;然后按照 Future 聲明的先后順序,打印 f1。 * 隨后到了 fx,由于 fx 的執行體是 null,相當于執行完畢了,Dart 將 fx 的 then 放入微任務隊列,由于微任務隊列的優先級最高,因此 fx 的 then 還是會最先執行,打印 f10。 * 然后到了 fx 下面的 f2,打印 f2,然后執行 then,打印 f3。f4 是一個微任務,要到下一個事件循環才執行,因此后續的 then 繼續同步執行,打印 f5。本次事件循環結束,下一個事件循環取出 f4 這個微任務,打印 f4。 * 然后到了 f2 下面的 f6,打印 f6,然后執行 then。這里需要注意的是,這個 then 是一個 Future 異步任務,因此這個 then,以及后續的 then 都被放入到事件隊列中了。 * f6 下面還有 f9,打印 f9。 * 最后一個事件循環,打印 f7,以及后續的 f8。 上面的代碼很是燒腦,萬幸我們平時開發 Flutter 時一般不會遇到這樣奇葩的寫法,所以你大可放心。你只需要記住一點:**then 會在 Future 函數體執行完畢后立刻執行,無論是共用同一個事件循環還是進入下一個微任務。** 在深入理解 Future 異步任務的執行規則之后,我們再來看看怎么封裝一個異步函數。 ## 異步函數 對于一個異步函數來說,其返回時內部執行動作并未結束,因此需要返回一個 Future 對象,供調用者使用。調用者根據 Future 對象,來決定:是在這個 Future 對象上注冊一個 then,等 Future 的執行體結束了以后再進行異步處理;還是一直同步等待 Future 執行體結束。 對于異步函數返回的 Future 對象,如果調用者決定同步等待,則需要在調用處使用 await 關鍵字,并且在調用處的函數體使用 async 關鍵字。 在下面的例子中,異步方法延遲 3 秒返回了一個 Hello 2019,在調用處我們使用 await 進行持續等待,等它返回了再打印: ~~~ // 聲明了一個延遲 3 秒返回 Hello 的 Future,并注冊了一個 then 返回拼接后的 Hello 2019 Future<String> fetchContent() => Future<String>.delayed(Duration(seconds:3), () => "Hello") .then((x) => "$x 2019"); main() async{ print(await fetchContent());// 等待 Hello 2019 的返回 } ~~~ 也許你已經注意到了,我們在使用 await 進行等待的時候,在等待語句的調用上下文函數 main 加上了 async 關鍵字。為什么要加這個關鍵字呢? 因為**Dart 中的 await 并不是阻塞等待,而是異步等待**。Dart 會將調用體的函數也視作異步函數,將等待語句的上下文放入 Event Queue 中,一旦有了結果,Event Loop 就會把它從 Event Queue 中取出,等待代碼繼續執行。 接下來,為了幫助你加深印象,我準備了兩個具體的案例。 我們先來看下這段代碼。第二行的 then 執行體 f2 是一個 Future,為了等它完成再進行下一步操作,我們使用了 await,期望打印結果為 f1、f2、f3、f4: ~~~ Future(() => print('f1')) .then((_) async => await Future(() => print('f2'))) .then((_) => print('f3')); Future(() => print('f4')); ~~~ 實際上,當你運行這段代碼時就會發現,打印出來的結果其實是 f1、f4、f2、f3! 我來給你分析一下這段代碼的執行順序: * 按照任務的聲明順序,f1 和 f4 被先后加入事件隊列。 * f1 被取出并打印;然后到了 then。then 的執行體是個 future f2,于是放入 Event Queue。然后把 await 也放到 Event Queue 里。 * 這個時候要注意了,Event Queue 里面還有一個 f4,我們的 await 并不能阻塞 f4 的執行。因此,Event Loop 先取出 f4,打印 f4;然后才能取出并打印 f2,最后把等待的 await 取出,開始執行后面的 f3。 由于 await 是采用事件隊列的機制實現等待行為的,所以比它先在事件隊列中的 f4 并不會被它阻塞。 接下來,我們再看另一個例子:在主函數調用一個異步函數去打印一段話,而在這個異步函數中,我們使用 await 與 async 同步等待了另一個異步函數返回字符串: ~~~ // 聲明了一個延遲 2 秒返回 Hello 的 Future,并注冊了一個 then 返回拼接后的 Hello 2019 Future<String> fetchContent() => Future<String>.delayed(Duration(seconds:2), () => "Hello") .then((x) => "$x 2019"); // 異步函數會同步等待 Hello 2019 的返回,并打印 func() async => print(await fetchContent()); main() { print("func before"); func(); print("func after"); } ~~~ 運行這段代碼,我們發現最終輸出的順序其實是“func before”“func after”“Hello 2019”。func 函數中的等待語句似乎沒起作用。這是為什么呢? 同樣,我來給你分析一下這段代碼的執行順序: * 首先,第一句代碼是同步的,因此先打印“func before”。 * 然后,進入 func 函數,func 函數調用了異步函數 fetchContent,并使用 await 進行等待,因此我們把 fetchContent、await 語句的上下文函數 func 先后放入事件隊列。 * await 的上下文函數并不包含調用棧,因此 func 后續代碼繼續執行,打印“func after”。 * 2 秒后,fetchContent 異步任務返回“Hello 2019”,于是 func 的 await 也被取出,打印“Hello 2019”。 通過上述分析,你發現了什么現象?那就是 await 與 async 只對調用上下文的函數有效,并不向上傳遞。因此對于這個案例而言,func 是在異步等待。如果我們想在 main 函數中也同步等待,需要在調用異步函數時也加上 await,在 main 函數也加上 async。 經過上面兩個例子的分析,你應該已經明白 await 與 async 是如何配合,完成等待工作的了吧。 介紹完了異步,我們再來看在 Dart 中,如何通過多線程實現并發。 ## Isolate 盡管 Dart 是基于單線程模型的,但為了進一步利用多核 CPU,將 CPU 密集型運算進行隔離,Dart 也提供了多線程機制,即 Isolate。在 Isolate 中,資源隔離做得非常好,每個 Isolate 都有自己的 Event Loop 與 Queue,**Isolate 之間不共享任何資源,只能依靠消息機制通信,因此也就沒有資源搶占問題**。 和其他語言一樣,Isolate 的創建非常簡單,我們只要給定一個函數入口,創建時再傳入一個參數,就可以啟動 Isolate 了。如下所示,我們聲明了一個 Isolate 的入口函數,然后在 main 函數中啟動它,并傳入了一個字符串參數: ~~~ doSth(msg) => print(msg); main() { Isolate.spawn(doSth, "Hi"); ... } ~~~ 但更多情況下,我們的需求并不會這么簡單,不僅希望能并發,還希望 Isolate 在并發執行的時候告知主 Isolate 當前的執行結果。 對于執行結果的告知,Isolate 通過發送管道(SendPort)實現消息通信機制。我們可以在啟動并發 Isolate 時將主 Isolate 的發送管道作為參數傳給它,這樣并發 Isolate 就可以在任務執行完畢后利用這個發送管道給我們發消息了。 下面我們通過一個例子來說明:在主 Isolate 里,我們創建了一個并發 Isolate,在函數入口傳入了主 Isolate 的發送管道,然后等待并發 Isolate 的回傳消息。在并發 Isolate 中,我們用這個管道給主 Isolate 發了一個 Hello 字符串: ~~~ Isolate isolate; start() async { ReceivePort receivePort= ReceivePort();// 創建管道 // 創建并發 Isolate,并傳入發送管道 isolate = await Isolate.spawn(getMsg, receivePort.sendPort); // 監聽管道消息 receivePort.listen((data) { print('Data:$data'); receivePort.close();// 關閉管道 isolate?.kill(priority: Isolate.immediate);// 殺死并發 Isolate isolate = null; }); } // 并發 Isolate 往管道發送一個字符串 getMsg(sendPort) => sendPort.send("Hello"); ~~~ 這里需要注意的是,在 Isolate 中,發送管道是單向的:我們啟動了一個 Isolate 執行某項任務,Isolate 執行完畢后,發送消息告知我們。如果 Isolate 執行任務時,需要依賴主 Isolate 給它發送參數,執行完畢后再發送執行結果給主 Isolate,這樣**雙向通信的場景我們如何實現呢**?答案也很簡單,讓并發 Isolate 也回傳一個發送管道即可。 接下來,我們以一個**并發計算階乘**的例子來說明如何實現雙向通信。 在下面的例子中,我們創建了一個異步函數計算階乘。在這個異步函數內,創建了一個并發 Isolate,傳入主 Isolate 的發送管道;并發 Isolate 也回傳一個發送管道;主 Isolate 收到回傳管道后,發送參數 N 給并發 Isolate,然后立即返回一個 Future;并發 Isolate 用參數 N,調用同步計算階乘的函數,返回執行結果;最后,主 Isolate 打印了返回結果: ~~~ // 并發計算階乘 Future<dynamic> asyncFactoriali(n) async{ final response = ReceivePort();// 創建管道 // 創建并發 Isolate,并傳入管道 await Isolate.spawn(_isolate,response.sendPort); // 等待 Isolate 回傳管道 final sendPort = await response.first as SendPort; // 創建了另一個管道 answer final answer = ReceivePort(); // 往 Isolate 回傳的管道中發送參數,同時傳入 answer 管道 sendPort.send([n,answer.sendPort]); return answer.first;// 等待 Isolate 通過 answer 管道回傳執行結果 } //Isolate 函數體,參數是主 Isolate 傳入的管道 _isolate(initialReplyTo) async { final port = ReceivePort();// 創建管道 initialReplyTo.send(port.sendPort);// 往主 Isolate 回傳管道 final message = await port.first as List;// 等待主 Isolate 發送消息 (參數和回傳結果的管道) final data = message[0] as int;// 參數 final send = message[1] as SendPort;// 回傳結果的管道 send.send(syncFactorial(data));// 調用同步計算階乘的函數回傳結果 } // 同步計算階乘 int syncFactorial(n) => n < 2 ? n : n * syncFactorial(n-1); main() async => print(await asyncFactoriali(4));// 等待并發計算階乘結果 ~~~ 看完這段代碼你是什么感覺呢?我們只是為了并發計算一個階乘,這樣是不是太繁瑣了? 沒錯,確實太繁瑣了。在 Flutter 中,像這樣執行并發計算任務我們可以采用更簡單的方式。Flutter 提供了支持并發計算的 compute 函數,其內部對 Isolate 的創建和雙向通信進行了封裝抽象,屏蔽了很多底層細節,我們在調用時只需要傳入函數入口和函數參數,就能夠實現并發計算和消息通知。 我們試著用 compute 函數改造一下并發計算階乘的代碼: ~~~ // 同步計算階乘 int syncFactorial(n) => n < 2 ? n : n * syncFactorial(n-1); // 使用 compute 函數封裝 Isolate 的創建和結果的返回 main() async => print(await compute(syncFactorial, 4)); ~~~ 可以看到,用 compute 函數改造以后,整個代碼就變成了兩行,現在并發計算階乘的代碼看起來就清爽多了。 ## 總結 好了,今天關于 Dart 的異步與并發機制、實現原理的分享就到這里了,我們來簡單回顧一下主要內容。 Dart 是單線程的,但通過事件循環可以實現異步。而 Future 是異步任務的封裝,借助于 await 與 async,我們可以通過事件循環實現非阻塞的同步等待;Isolate 是 Dart 中的多線程,可以實現并發,有自己的事件循環與 Queue,獨占資源。Isolate 之間可以通過消息機制進行單向通信,這些傳遞的消息通過對方的事件循環驅動對方進行異步處理。 在 UI 編程過程中,異步和多線程是兩個相伴相生的名詞,也是很容易混淆的概念。對于異步方法調用而言,代碼不需要等待結果的返回,而是通過其他手段(比如通知、回調、事件循環或多線程)在后續的某個時刻主動(或被動)地接收執行結果。 因此,從辯證關系上來看,異步與多線程并不是一個同等關系:異步是目的,多線程只是我們實現異步的一個手段之一。而在 Flutter 中,借助于 UI 框架提供的事件循環,我們可以不用阻塞的同時等待多個異步任務,因此并不需要開多線程。我們一定要記住這一點。 我把今天分享所涉及到的知識點打包到了[GitHub](https://github.com/cyndibaby905/23_dart_async)中,你可以下載下來,反復運行幾次,加深理解。 ## 思考題 最后,我給你留下兩道思考題吧。 1. 在通過并發 Isolate 計算階乘的例子中,我在 asyncFactoriali 方法里先后發給了并發 Isolate 兩個 SendPort。你能否解釋下這么做的原因?可以只發一個 SendPort 嗎? 2. 請改造以下代碼,在不改變整體異步結構的情況下,實現輸出結果為 f1、f2、f3、f4。 ~~~ Future(() => print('f1')) .then((_) async => await Future(() => print('f2'))) .then((_) => print('f3')); Future(() => print('f4'));
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看