天天看點

#IT明星不是夢# 一文讀懂Rust的async一文讀懂Rust的async

不同的程式設計語言表現異步程式設計的方式可能不一樣,Rust跟JavaScript的async/await類似:使用的關鍵字啊,程式設計模型啊都差不多啦! 也有些不一樣的地方,畢竟Rust是重新設計的語言嘛,比如:在JavaScript中使用Promise表示需要延遲異步執行的計算,在Rust中使用的是Future.在JavaScript中不需要選擇指定運作異步代碼的運作時,在Rust中需要. Rust還更麻煩了?還得選擇指定運作時?

這是因為Rust是可以面向硬體、嵌入式裝置的作業系統級别的程式設計語言就像C++,并且零抽象成本.這就需要Rust需要有選擇地把功能包含進标準庫裡.簡單來說,為了滿足不同的程式設計場景Rust标準庫就沒有包含指定異步代碼運作時,我們可以根據具體的場景選擇不同的運作時。

是不是感覺還有點暈乎?沒關系,下面我們會介紹怎麼在Rust中編寫異步(async)代碼.知道了怎麼編寫異步代碼,也就知道async是什麼了.如果你是第一次使用Rust編寫異步代碼或者第一次使用異步代碼庫正在迷茫從何入手,那恭喜你,這篇文檔特别适合你.開始之前我們先快速的介紹下異步程式設計的基本要素.

編寫異步的應用,至少需要倆個crate:

<code>futures</code>:這個是Rust官方團隊提供維護的crate.

異步代碼運作時crate: 可以自己選擇,比如:Tokio, async_std, smol等等.

你可能不想在項目中引入過多依賴,但這些依賴就像<code>chrono</code>和<code>log</code>是比較基礎的依賴.唯一的不同是這些依賴是面向異步程式設計的.

我們接下來會使用Tokio做為運作時,剛開始你最好也先了解熟悉使用一種運作時,然後再嘗試使用其它運作時。

因為這些運作時之間有很多<code>相通</code>的地方,熟悉了一個再去熟悉其它的就簡單了。就像我們學習程式設計語言一樣,學好學深一門程式設計語言,再去學習其它的語言就快了。不要一開始就幾門語言一起學,這樣很可能實際開發時這也不行那也不行換來換去還是不能開發出東西.

我們可以像下面這樣引入依賴:

在<code>main.rs</code>中敲入以下代碼:

可以執行下<code>cargo check</code>如果沒什麼報錯資訊,依賴配置就完成了.接下來我們介紹怎麼使用運作時。

像我們先前說的Rust标準庫并沒有指定異步代碼的運作時,是以我們自己選擇運作時并配置相應的依賴。這裡我們選擇了使用Tokio:

tokio = {version = "0.2.*", features = ["full"] }

有些第三方庫可能需要我們使用指定的異步代碼運作時,因為它們内部是對特定運作時庫進行了封裝。比如:web開發架構<code>actix_web</code>就是基于<code>tokio</code>封裝開發的.但大多少情況我們都可以自己選擇運作時。無論我們選擇那一種運作時,在開始編寫代碼前都需要先搞清除:

怎麼<code>啟動</code>運作時?

怎麼<code>生成 Future</code> ?

怎麼處理阻塞(IO密集)和CPU密集任務?

搞清除了這三個問題基本上也就學會怎麼編寫異步代碼了.接下來我們就以<code>tokio</code>為例示範下:

可以執行個體化一個運作時,并派生一個Future指定給運作時。這個Future就是異步代碼的主入口,可以把它想象成異步代碼的main函數:

還可以使用宏,簡化代碼為:

雖然代碼行數少了,功能跟上面的代碼還是一樣的哦!

你想并發運作多個任務時,就可以像這樣生成Future:

什麼是阻塞性的任務?什麼是CPU密集性的任務呢?可以簡單的了解為這兩種任務都會長時間的霸占CPU阻塞線程繼續執行其它任務.就好比工地上有個包工頭專門負責配置設定任務給小工門幹,有些小活小任務包工頭可能順手就幹了,但是一些耗時比較長的比如去搬一車磚頭,包工頭就不能自己去幹了,因為它去搬磚頭了就沒人負責任務配置設定了,小工們活都幹完了隻能等着包工頭配置設定任務才能繼續幹活.包工頭呢?還在搬磚頭呢.顯然這是會影響整體工作效率的。代碼也一樣,要有個專門負責總體配置設定任務的線程,在這個線程中就不能再執行其它比較耗費時間的的任務了。那耗費時間的任務誰來執行呢?小工呗,也就是派生新的Future. 就像這個樣子:

tokio是使用的spawn_blocking去派生新的Future使用新的線程執行比較耗時的任務,其它運作時庫可能API不一樣但也會提供類似的方法.

支援我們已經學習了怎麼使用Rust編寫異步代碼,接下來把所學内容整合到一起做個樣例:

使用到的crate有:

提供異步代碼運作時的 <code>tokio</code>

Rust日志門面<code>log</code>

日志工具<code>env_logger</code>

<code>Cargo.toml</code>類似這個樣子:

需要注意的是<code>env_logger</code>需要根據環境變量<code>RUST_LOG</code>設定日志級别

基本上所有的異步程式設計項目都可以使用類似這樣的依賴配置和<code>main.rs</code>.根據不同的使用場景還可以優化下錯誤處理和日志.比如可以考慮使用 Anyhow處理錯誤,可以考慮使用 async-log更好的在異步多線程環境中輸出日志.在本文檔中接下來的代碼就基于這個樣例模闆開發了。

在Rust中編寫異步函數跟先前編寫普通函數有點不一樣.先前接觸Rust函數時,你可能已經注意到函數的參數傳回值都需要聲明确切的類型。異步函數的傳回值都是經過<code>Future</code>包裝的。如果你讀了關于<code>Future</code>的文檔,按照這個思路你可能認為應該像下面這樣編寫異步函數:

不用這麼麻煩,比較Rust是重新設計的語言.當你使用async關鍵字時,Rust會自動的使用Future封裝傳回隻,是以你原來怎麼給普通函數定義傳回值就繼續那麼地幹,就像這個樣子:

這裡使用的<code>future::ok</code>是future庫提供的友善我們開發使用的,用于生成狀态為<code>ready</code>的<code>future</code>.

你可能會見到使用異步代碼塊<code>async {...}</code>建立異步代碼的,這是為了更靈活的定義傳回值類型,不過大多少情況下使用異步函數就夠了.接下來我們編寫一個使用異步函數的例子.

在Rust中<code>future</code>是<code>lazy</code>(懶)的.也就是說,預設情況下當你建立了一個future,它是什麼都不幹的,非得等你調用<code>await</code>告訴它該幹活了,它才開始幹活.

接下來我們以發起處理Web請求的場景用代碼示範一下子:

建立web請求使用到了reqwest庫,需要把這個庫添加到Cargo.toml的依賴區域:

reqwest = "0.10.*"

執行上面的代碼輸出類似這個樣子:

這裡的日志格式是自定義的,前面的數字是程式執行的時間,自定義日志格式的代碼是這個樣子地:

從日志輸出可以看出,我們的函數并不是一起執行的,而是一個執行完成後另一個才開始執行的,因為我們這裡還是使用的普通函數并沒有使用異步函數.接下來是修改為異步函數的版本:

tokio提供的spawn函數可以讓我們使用多線程并發執行異步函數.

執行的效果類似這個樣子:

可以跟上面使用普通函數的方式對比一下子,是不是總體效率快多了,倆個請求不需要互相等待,各自說幹就幹,就是這麼快.

什麼時候該派生Future執行任務呢?這裡有幾條建議

優先選用沒有阻塞的操作庫.

如果不确定就派生一個吧.

學員專享pdf版本請點這裡下載下傳

參考原文

繼續閱讀