異步編程需要“意識(shí)”

雖然我們生活在一個(gè)異步的世界里,但對(duì)于多數(shù)編程初學(xué)者來說,異步還是很陌生。學(xué)習(xí)一門編程語(yǔ)言,通常都是從同步流程開始的,即順序、分支和循環(huán)。而異步流程是什么呢——開始一個(gè)異步調(diào)用,然后……就沒有然后了。異步程序跑哪去了?

我們提供的服務(wù)有:做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、新野ssl等。為1000多家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的新野網(wǎng)站制作公司

異步程序會(huì)以某種異步的形式在運(yùn)行著,比如多線程、異步IO等,直到處理完成。那如果需要處理結(jié)果怎么辦?給一個(gè)程序入口,讓它處理完當(dāng)前過程之后,把處理結(jié)果送到這個(gè)入口,然后執(zhí)行另一段程序——俗稱回調(diào)?;卣{(diào)一般使用 callback 這個(gè)名稱,不過有時(shí)候我更喜歡使用 next,因?yàn)樗碇乱粋€(gè)處理步驟。

同步和異步的概念

現(xiàn)在我們接觸到了一些概念,比如同步和異步,它們是什么?

這兩個(gè)概念并不來源于編程語(yǔ)言,而是來源于低層指令,甚至更低層的——電路。它們是基于時(shí)序的兩個(gè)概念,其中,“步”是指步調(diào),所以同步表示相同的步調(diào),而異步表示不同的步調(diào)。當(dāng)然這兩個(gè)概念提升到程序這個(gè)級(jí)別的時(shí)候,精確的意思與時(shí)鐘無關(guān),但所表示的意義仍然未變。

同步

舉個(gè)生活中的例子來說明這個(gè)問題——排除買票。售票廳開了一個(gè)窗口,有一隊(duì)人在排隊(duì)依次買票。這個(gè)隊(duì)伍中,前面一個(gè)人往前走了一步,后面的人才能往前走一步;前面的人在等待,后面的人就一定在等待。那么在理想的情況下,所有人可以同時(shí)向前邁步。OK,大家步伐一致,稱為同步。

這里把售票窗口看作是處理器,每個(gè)人看作是等待執(zhí)行的指令,買票這個(gè)動(dòng)作就是在執(zhí)行指令。它的特點(diǎn)是按步就班,如果一個(gè)人買票時(shí)間過長(zhǎng)(指令執(zhí)行時(shí)間過長(zhǎng)),就會(huì)造成阻塞。

異步(多線程)

現(xiàn)在買票的人漸漸多起來,所以售票廳多開了幾個(gè)窗口同時(shí)售票。每個(gè)單獨(dú)的隊(duì)伍仍然保持著同步,但不同的隊(duì)伍之間,步伐不再一致,稱為異步。A 隊(duì)列售票很順利,隊(duì)伍在有序快速的前進(jìn),但 B 隊(duì)列的某個(gè)顧客似乎在付費(fèi)時(shí)遇到點(diǎn)麻煩,花了很長(zhǎng)的時(shí)候,造成阻塞,但這對(duì) A 隊(duì)列并不產(chǎn)生影響。

這時(shí)候的售票廳可以看作是在以多線程的方式運(yùn)行著異步程序。從這個(gè)例子可以看到異步的兩個(gè)特點(diǎn):其一,兩個(gè)異步流程之間相互獨(dú)立,它們相互不會(huì)阻塞(有個(gè)前提,不需要等待共享資源的情況下);其二,異步程序內(nèi)部仍然是同步的。

異步(IO)

上面的例子比較符合多線程異步的情況。那 IO 異步又是什么樣呢?

年底了,M 在準(zhǔn)備年終匯報(bào)的資料,這可是個(gè)緊張的工作(CPU),要收集不少數(shù)據(jù)來寫好些文案。為了其中一份文案,M 需要車間的生產(chǎn)數(shù)據(jù),但跑一趟車間(IO)可需要花不少時(shí)間,所以他讓 N 去車間收集數(shù)據(jù),自己則繼續(xù)寫其它方案,同時(shí)等 N 把數(shù)據(jù)收集回來(啟動(dòng)異步程序)。半天以后,N 帶回了數(shù)據(jù)(插入事件消息),M 繼續(xù)完成手上的文案(完成當(dāng)前事件循環(huán)),之后使用 N 帶回來的數(shù)據(jù)開始撰寫關(guān)于車間的報(bào)告(新的事件循環(huán))……

IO 的處理速度比 CPU 慢得多,所以 IO 異步讓 CPU 不必閑置著等待 IO 操作完成。當(dāng) IO 操作完成之后,CPU 會(huì)適地使用 IO 操作結(jié)果繼續(xù)工作。

同步邏輯和異步邏輯

回到程序上來,我們以一個(gè)函數(shù)的處理過程來描述同步和異步的處理方式。

同步邏輯

那么,同步處理過程是:

接受輸入 ? 處理 ? 產(chǎn)生輸出

用一段偽代碼來描述就是

注:本文中的偽代碼比較接近 JavaScript 語(yǔ)法,而有時(shí)候?yàn)榱苏f明類型,采用了 TypeScript 的類型申明語(yǔ)法。

function func(input) {
    do something with input
    return output
}

這是標(biāo)準(zhǔn)的 IPO(Input-Process-Output) 處理。

異步邏輯

而異步呢,是:

接受輸入 ? 處理 ? 啟動(dòng)下一步(如果有)

用偽代碼來描述就是:

function asyncFunc(input, next) {
    do something with input
    if (next is a entry) {
        next(output)
    }
}

這個(gè)過程稱為 IPN(Input-Process-Next)。

注意到這里的 Next,下一步,只有一步。這一步,囊括了后續(xù)的若干步驟。所以這一步,只能是后續(xù)若干步驟封裝出來一個(gè)模塊入口,或者說函數(shù)。

因此,模塊化思想在異步思維中是一個(gè)非常關(guān)鍵的思想。很多初學(xué)者寫代碼喜歡像記流水賬一樣一句句往下寫,動(dòng)不動(dòng)就是成百上千行的函數(shù),這就是一種缺乏模塊化思想的表現(xiàn)。模塊化思想需要訓(xùn)練,分析代碼的相關(guān)性,提煉函數(shù),提取對(duì)象,在具有一定經(jīng)驗(yàn)之后還需要掌握模塊細(xì)化的粒度平衡。這不是一朝一夕之功,不過我推薦看看“設(shè)計(jì)模式”和“重構(gòu)”相關(guān)的書籍。

異步開發(fā)工具(SDK和語(yǔ)法層面的)

承諾(Promise)

再想想上面關(guān)于年終匯報(bào)的例子,M 請(qǐng) N 去車間收集數(shù)據(jù)的時(shí)候,N 會(huì)說:“好的,我很快就把數(shù)據(jù)帶回來”,這是一種承諾?;谶@個(gè)承諾,M 才能安排后面撰寫關(guān)于車間的匯報(bào)材料。這個(gè)過程用偽代碼來描述就是

function collectData(): Promise {
    // N 去收集數(shù)據(jù),產(chǎn)生了一個(gè)承諾
    return new Promise(resolve => {
        collect data from workshop
        // 這個(gè)承諾最終會(huì)帶來數(shù)據(jù)
        resolve(data)
    })
}

function writeWorkshopReport(data) {
    write report with data
}

// 收集數(shù)據(jù)的承諾兌現(xiàn)之后,可將這個(gè)數(shù)據(jù)用于寫報(bào)告
collectData()
    .then(data => writeWorkshopReport(data))

以 JavaScript 為代表的一些語(yǔ)言 SDK 中使用了 Promise。不過 C# 中是采用的 TaskTask<T>,相應(yīng)的,使用了 Task.ContinueWithTask<T>.ContinueWith 來代替 Promise.then。

異步邏輯同步化

上面提到了同步思維和異步思維在一個(gè)處理步驟中的區(qū)別。如果跳出一個(gè)處理步驟,從更大范圍的處理流程來看,異步與同步其實(shí)也沒多大區(qū)別,都是 輸入-->處理-->產(chǎn)生輸出-->將輸出用于下一步驟,唯一要注意的是需要等待異步處理產(chǎn)生的輸出,我們可以稱之為異步等待。由于我們可以一邊進(jìn)行異步等待(async wait,簡(jiǎn)寫 await),一邊做別的事情,所以這個(gè)等待并不產(chǎn)生阻塞。但是,由于聲明了這個(gè)等待,編譯器/解釋器會(huì)將后面的代碼自動(dòng)放在等待完成之后調(diào)用,這讓異步代碼寫起來就像寫同步代碼一樣。

上面的例子使用異步等待的偽代碼會(huì)像這樣

async function collectData(): Promise {
    collect data from workshop
    // 多數(shù)語(yǔ)言會(huì)把 async 函數(shù)的返回值封裝成 Promise
    return data
}

function writeWorkshopReport(data) {
    write report with data
}

// await 只能用于聲明為 async 的函數(shù)中
async function main() {
    data = await collectData()
    writeWorkshopReport(data)
}

// 定義了異步 main 函數(shù),一定要記得調(diào)用,不然它是不會(huì)執(zhí)行的
main()

像 C# 和 JavaScript 等語(yǔ)言都從語(yǔ)法層面規(guī)定了 await 必須用在聲明為 async 的函數(shù)中,這就從編譯/解釋的層面限定了 await 的用途,只要使用了 await,那它所處的就一定是一個(gè)異步上下文。而 async 也要求編譯器/解釋器對(duì)其返回值進(jìn)行一些自動(dòng)處理,比如在 JavaScript 中,其返回值如果不是 Promise 對(duì)象,它會(huì)自動(dòng)封裝成一個(gè) Promise 對(duì)象;而在 C# 中,它會(huì)自動(dòng)封裝成 TaskTask<T>(所以 async 方法的類型需要聲明為 TaskTask<T>)。

注意,注意,注意

盡管語(yǔ)言服務(wù)在異步程序同步化方面已經(jīng)做了很多工作,但是仍然避免不了一些人為錯(cuò)誤,比如忘記寫 await 關(guān)鍵字。在強(qiáng)類型語(yǔ)言中編譯器會(huì)檢查得嚴(yán)格一些,但如果是在 JavaScript 中,忘記寫 await 意味著原本應(yīng)該取得一個(gè)值的語(yǔ)句,會(huì)取到一個(gè) Promise。解釋器不會(huì)對(duì)此質(zhì)疑,但程序運(yùn)行的結(jié)果會(huì)不正確。

小結(jié)

總的來說,異步編程并不是特別困難的事情。使用 async/await 語(yǔ)言特性甚至可以用類似編寫同步代碼的方法來編寫異步代碼。但語(yǔ)法糖終究是糖,要想把異步編程掌握得更好,還是需要去了解和熟悉異步、回調(diào)、Promise、模塊化、設(shè)計(jì)模式、重構(gòu)等概念。

相關(guān)閱讀

  • 從小小題目逐步走進(jìn) JavaScript 異步調(diào)用
  • 閑談異步調(diào)用“扁平”化
  • 理解 JavaScript 的 async/await
  • C# 并行計(jì)算(Parallel 和 ParallelQuery)

文章題目:異步編程需要“意識(shí)”
文章轉(zhuǎn)載:http://bm7419.com/article26/jdjccg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、網(wǎng)站營(yíng)銷網(wǎng)站維護(hù)、搜索引擎優(yōu)化、App開發(fā)網(wǎng)站收錄

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁(yè)設(shè)計(jì)