<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Swift 新并發(fā)框架之 async/await

          共 15568字,需瀏覽 32分鐘

           ·

          2022-06-26 11:25

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????

          即使對于經(jīng)驗(yàn)豐富的開發(fā)者來說,寫出健壯性、可維護(hù)性高的并發(fā)代碼也是一項(xiàng)具有挑戰(zhàn)性的任務(wù),其挑戰(zhàn)主要體現(xiàn)在兩個方面:

          • 傳統(tǒng)并發(fā)模型是基于異步模式,代碼維護(hù)性不夠友好;
          • 并發(fā)往往意味著 Data Races,這是一類難復(fù)現(xiàn)、難排查的常見問題。

          Swift 在 5.5 開始引入的新并發(fā)框架主要著力解決這 2 個問題。

          本文是 『 Swift 新并發(fā)框架 』系列文章的第一篇,主要介紹 Swift 5.5 引入的 async/await。

          本系列文章對 Swift 新并發(fā)框架中涉及的內(nèi)容逐個進(jìn)行介紹,內(nèi)容如下:

          • Swift 新并發(fā)框架之 async/await
          • Swift 新并發(fā)框架之 actor[1]
          • Swift 新并發(fā)框架之 Sendable[2]
          • Swift 新并發(fā)框架之 Task[3]

          Overview

          在正式開始前,簡單回顧一下同步/異步、串行/并行的概念:

          • 同步(Synchronous)、異步(Asynchronous) 通常指方法(/函數(shù)),同步方法表示直到任務(wù)完成才返回,異步方法則是將任務(wù)拋出去,在任務(wù)完成前就返回;

            這也就意味著需要通過某種方式獲得異步任務(wù)的結(jié)果,如:Delegate、Closure 等。

          • 串行(Serial)、并行(Concurrent) 通常指 App 執(zhí)行一組任務(wù)的模式,串行表示一次只能執(zhí)行一個任務(wù),只有當(dāng)前任務(wù)完成后才啟動下一個任務(wù),而并行指可以同時執(zhí)行多個任務(wù)。最常見的莫過于 GCD 中的串行、并行隊(duì)列;

            ps. 在此我們不嚴(yán)格區(qū)分并發(fā)、并行的區(qū)別。

          • 傳統(tǒng)的并發(fā)模型都是基于異步模式的,即異步獲取并發(fā)任務(wù)的結(jié)果。

          同步代碼是線性的 (straight-line),非常適合人腦處理。

          而異步代碼是非線性的、跳躍式的 (類似于 goto 語句),對于單核的人腦來說是一大挑戰(zhàn)。

          除了在閱讀上對人腦思維模式構(gòu)成較大挑戰(zhàn)外,異步代碼在具體實(shí)現(xiàn)上常伴有以下問題:

          • 回調(diào)地獄 (Callback Hell);
          • 錯誤處理 (Error Handling);
          • 容易出錯。

          初探

          我們先通過一個簡單的例子對比一下傳統(tǒng)并發(fā)模型與新的并發(fā)模型間的區(qū)別。

          該例子通過 token 獲取頭像,其步驟有:

          • 通過 token 獲取頭像 URL;
          • 通過 URL 下載頭像數(shù)據(jù)(加密);
          • 對頭像數(shù)據(jù)解密;
          • 圖片解碼。
          class AvatarLoader {
            func loadAvatar(token: String, completion: (Image) -> Void) {
              fetchAvatarURL(token: token) { url in
                fetchAvatar(url: url) { data in
                  decryptAvatar(data: data) { data in
                    decodeImage(data: data) { image in
                      completion(image)
                    }
                  }
                }
              }
            }

            func fetchAvatarURL(token: String, completion: (String) -> Void) {
              // fetch url from net...
              completion(avatarURL)
            }

            func fetchAvatar(url: String, completion: (Data) -> Void) {
              // download avatar data...
              completion(avatarData)
            }

            func decryptAvatar(data: Data, completion: (Data) -> Void) {
              // decrypt...
              completion(decryptedData)
            }

            func decodeImage(data: Data, completion: (Image) -> Void) {
              // decode...
              completion(avatar)
            }
          }

          loadAvatar 方法中回調(diào)層級之深不言而喻。

          上述代碼還遺漏了一個重要問題:錯誤處理,其中的網(wǎng)絡(luò)請求、解密、解碼都有可能出錯。

          優(yōu)雅地處理錯誤是一項(xiàng)非??简?yàn)基本功的任務(wù)。

          一般地,錯誤處理分為 2 種情況:

          • 同步方法:優(yōu)先考慮通過 throw 拋出error,這樣調(diào)用方就不得不處理錯誤,因此帶有一定的強(qiáng)制性;
          • 異步方法:在回調(diào)中傳遞 error,這種情況下調(diào)用方通常會有意無意地忽略錯誤,使健壯性大打折扣。

          為了處理錯誤,對上述代碼進(jìn)行升級:

          class AvatarLoader {
            func loadAvatar(token: String, completion: (Image?, Error?) -> Void) {
              fetchAvatarURL(token: token) { url, error  in
                guard let url = url else {
                  // 在這個路徑,經(jīng)常容易漏掉執(zhí)行 completion 或者 return 語句
                  completion(nil, error)
                  return
                }

                fetchAvatar(url: url) { data, error in
                  guard let data = data else {
                    completion(nil, error)
                    return
                  }

                  decryptAvatar(data: data) { data, error in
                    guard let data = data else {
                      completion(nil, error)
                      return
                    }

                    decodeImage(data: data) { image, error in
                      completion(image, error)
                    }
                  }
                }
              }
            }

            func fetchAvatarURL(token: String, completion: (String?, Error?) -> Void) {
              // fetch url from net...
              completion(avatarURL, error)
            }

            func fetchAvatar(url: String, completion: (Data?, Error?) -> Void) {
              // download avatar data...
              completion(avatarData, error)
            }

            func decryptAvatar(data: Data, completion: (Data?, Error?) -> Void) {
              // decrypt...
              completion(decryptedData, error)
            }

            func decodeImage(data: Data, completion: (Image?, Error?) -> Void) {
              // decode...
              completion(avatar, error)
            }
          }

          可以看到,為了處理錯誤,在 completion 中增加了 error 參數(shù),同時需要將 2 個參數(shù)都定義成 Optional。

          同時,在 loadAvatar 中添加了大量的 guard,這樣的代碼無疑非常丑陋。

          Optional 無形中增加了代碼成本。

          為此,Swift 5 引入了 Result 用于優(yōu)化上述錯誤處理場景:

          class AvatarLoader {
            func loadAvatar(token: String, completion: (Result<Image, Error>) -> Void) {
              fetchAvatarURL(token: token) { result in
                switch result {
                case let .success(url):
                  fetchAvatar(url: url) { result in
                    switch result {
                    case let .success(decryptData):
                      decryptAvatar(data: decryptData) { result in
                        switch result {
                        case let .success(avaratData):
                          decodeImage(data: avaratData) { result in
                            completion(result)
                          }

                        case let .failure(error):
                          completion(.failure(error))
                        }
                      }
                    case let .failure(error):
                      completion(.failure(error))
                    }
                  }
                case let .failure(error):
                  completion(.failure(error))
                }
              }
            }

            func fetchAvatarURL(token: String, completion: (Result<String, Error>) -> Void) {
              // fetch url from net...
              completion(.success(avatarURL))
            }

            func fetchAvatar(url: String, completion: (Result<Data, Error>) -> Void) {
              // download avatar data...
              completion(.success(avatarData))
            }

            func decryptAvatar(data: Data, completion: (Result<Data, Error>) -> Void) {
              // decrypt...
              completion(.success(decryptData))
            }

            func decodeImage(data: Data, completion: (Result<Image, Error>) -> Void) {
              // decode...
              completion(.success(avatar))
            }
          }

          Result 是 enum 類型,含有 success、failure 2 個 case。

          可以看到,通過使用 Result,參數(shù)不必是 Optional,另外可以通過 switch/case 來處理結(jié)果,在一定程度保證了調(diào)用方對錯誤的處理。

          在上面這個 Callback Hell 中,直觀上, Result 不但沒有使代碼簡潔,反而更加復(fù)雜了。

          主要是沒有把代碼抽離開來,不要對 Result 有什么誤解^__^。

          通過這個簡單的例子,可以看到基于 Callback 的異步模型問題不少。

          因此,將異步代碼同步化一直是業(yè)界努力的方向。

          如:Promise[4],不過其同步也是建立在 callback 基礎(chǔ)上的。

          Swift 5.5 引入了 async/await 用于將異步代碼同步化。

          很多語言都已支持 async/await,如:JavaScript、Dart 等

          先直觀感受一下 async/await

          class AvatarLoader {
            func loadAvatar(token: String) async throws -> Image {
              let url = try await fetchAvatarURL(token: token)
              let encryptedData = try await fetchAvatar(url: url)
              let decryptedData = try await decryptAvatar(data: encryptedData)
              return try await decodeImage(data: decryptedData)
            }

            func fetchAvatarURL(token: String) async throws -> String {
              // fetch url from net...
              return avatarURL
            }

            func fetchAvatar(url: String) async throws -> Data {
              // download avatar data...
              return avatarData
            }

            func decryptAvatar(data: Data) async throws -> Data {
              // decrypt...
              return decryptData
            }

            func decodeImage(data: Data) async throws -> Image {
              // decode...
              return avatar
            }
          }

          相比基于 Callback 的異步版本,基于 async/await 的版本是不是清晰多了。

          尤其是 loadAvatar 方法從感觀上就是一個同步方法,閱讀起來無比順暢。

          其錯誤處理也使用了同步式的 throws。

          至此,通過對比,對 async/await 有了一個較直觀的認(rèn)識,下面簡單探討一下其實(shí)現(xiàn)機(jī)制。

          深究

          首先,還是有必要對 async/await 作一個正式的介紹:

          • async — 用于修飾方法,被修飾的方法則被稱為異步方法 (asynchronous method),異步方法意味著其在執(zhí)行過程中可能會被暫停 (掛起);

          • await — 對 asynchronous method 的調(diào)用需加上 await。同時,await只能出現(xiàn)在異步上下文中 (asynchronous context);

            await 則表示一個潛在暫停點(diǎn) (potential suspension points)。

          什么是 asynchronous context ?其存在于 2 種環(huán)境下:

          • asynchronous method body — 異步方法體屬于異步上下文的范疇;

          • Task closure — Task 任務(wù)閉包也屬于 asynchronous context。

            Task 是在 Swift 5.5 中引入的,主要用于創(chuàng)建、執(zhí)行異步任務(wù)。

          因此,只能在異步方法或 Task 閉包中通過 await 調(diào)用異步方法。

          異步方法執(zhí)行過程中可能會暫停?

          potential suspension points?

          怎么暫停?

          剛開始接觸 async/await 時,下意識地可能會有這些疑問。

          2個關(guān)鍵點(diǎn):

          • 暫停的是方法,而不是執(zhí)行方法的線程;
          • 暫停點(diǎn)前后可能會發(fā)生線程切換。

          在 Swift 新并發(fā)模型中進(jìn)一步弱化了『 線程 』,理想情況下整個 App 的線程數(shù)應(yīng)與內(nèi)核數(shù)一致,線程的創(chuàng)建、管理完全交由并發(fā)框架負(fù)責(zé)。

          Swift 對異步方法 (asynchronous method) 的處理就遵守了上述思想:

          • 異步方法被暫停點(diǎn) (suspension points) 分割為若干個 Job;
          • 在并發(fā)框架中 Job 是任務(wù)調(diào)度的基本單元;
          • 并發(fā)框架根據(jù)實(shí)時情況動態(tài)決定某個 Job 的執(zhí)行線程;
          • 也就是同一個異步方法中的不同 Job 可能運(yùn)行在不同線程上。

          正是由于異步方法在其暫停點(diǎn)前后可能會變換執(zhí)行線程,因此在異步方法中要慎用鎖、信號量等同步操作。

          let lock = NSLock.init()
          func test() async {
            lock.lock()
            try? await Task.sleep(nanoseconds: 1_000_000_000)
            lock.unlock()
          }

          for i in 0..<10 {
            Task {
              await test()
            }
          }

          像上面這樣的代碼在 lock.lock() 處會產(chǎn)生死鎖,換成信號量也是一樣。

          await 之所以稱為『 潛在 』暫停點(diǎn),而不是暫停點(diǎn),是因?yàn)椴⒉皇撬械?await 都會暫停,只有遇到類似 IO、手動起子線程等情況時才會暫停當(dāng)前調(diào)用棧的運(yùn)行。

          總之,對于異步方法如何切分 Job 等細(xì)節(jié)可以不關(guān)心,await 可能會暫停當(dāng)前方法的運(yùn)行,并在時機(jī)成熟后在其他線程恢復(fù)運(yùn)行是我們需要明確了解的。

          參考資料

          swift-evolution/0296-async-await.md at main · apple/swift-evolution · GitHub[5]

          swift-evolution/0302-concurrent-value-and-concurrent-closures.md at main · apple/swift-evolution · GitHub[6]

          swift-evolution/0337-support-incremental-migration-to-concurrency-checking.md at main · apple/swift-evolution · GitHub[7]

          swift-evolution/0304-structured-concurrency.md at main · apple/swift-evolution · GitHub[8]

          swift-evolution/0306-actors.md at main · apple/swift-evolution · GitHub[9]

          swift-evolution/0337-support-incremental-migration-to-concurrency-checking.md at main · apple/swift-evolution · GitHub[10]

          Understanding async/await in Swift ? Andy Ibanez[11]

          Concurrency — The Swift Programming Language (Swift 5.6)[12]

          Connecting async/await to other Swift code | Swift by Sundell[13]

          參考資料

          [1]

          Swift 新并發(fā)框架之 actor: https://juejin.cn/post/7076738494869012494

          [2]

          Swift 新并發(fā)框架之 Sendable: https://juejin.cn/post/7076741945820872717

          [3]

          Swift 新并發(fā)框架之 Task: https://juejin.cn/post/7084640887250092062/

          [4]

          Promise: https://www.promisejs.org/

          [5]

          swift-evolution/0296-async-await.md at main · apple/swift-evolution · GitHub: https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md

          [6]

          swift-evolution/0302-concurrent-value-and-concurrent-closures.md at main · apple/swift-evolution · GitHub: https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md

          [7]

          swift-evolution/0337-support-incremental-migration-to-concurrency-checking.md at main · apple/swift-evolution · GitHub: https://github.com/apple/swift-evolution/blob/main/proposals/0337-support-incremental-migration-to-concurrency-checking.md

          [8]

          swift-evolution/0304-structured-concurrency.md at main · apple/swift-evolution · GitHub: https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md#jobs

          [9]

          swift-evolution/0306-actors.md at main · apple/swift-evolution · GitHub: https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md

          [10]

          swift-evolution/0337-support-incremental-migration-to-concurrency-checking.md at main · apple/swift-evolution · GitHub: https://github.com/apple/swift-evolution/blob/main/proposals/0337-support-incremental-migration-to-concurrency-checking.md

          [11]

          Understanding async/await in Swift ? Andy Ibanez: https://www.andyibanez.com/posts/understanding-async-await-in-swift/

          [12]

          Concurrency — The Swift Programming Language (Swift 5.6): https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#

          [13]

          Connecting async/await to other Swift code | Swift by Sundell: https://www.swiftbysundell.com/articles/connecting-async-await-with-other-swift-code/

          作者:峰之巔 

          https://juejin.cn/post/7076733264798416926

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 50
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码一区二区三区四区五区在线看 | 91麻豆精品国产91久久久吃药 | 免费视频久久 | 求毛片网址 | 丁香五月天婷婷激情 |