<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

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

    共 15568字,需瀏覽 32分鐘

     ·

    2022-06-26 11:25

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

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

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

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

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

    本系列文章對 Swift 新并發(fā)框架中涉及的內(nèi)容逐個進行介紹,內(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ù),只有當前任務(wù)完成后才啟動下一個任務(wù),而并行指可以同時執(zhí)行多個任務(wù)。最常見的莫過于 GCD 中的串行、并行隊列;

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

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

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

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

    除了在閱讀上對人腦思維模式構(gòu)成較大挑戰(zhàn)外,異步代碼在具體實現(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)雅地處理錯誤是一項非??简灮竟Φ娜蝿?wù)。

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

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

    為了處理錯誤,對上述代碼進行升級:

    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 有了一個較直觀的認識,下面簡單探討一下其實現(xiàn)機制。

    深究

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

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

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

      await 則表示一個潛在暫停點 (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)鍵點:

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

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

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

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

    正是由于異步方法在其暫停點前后可能會變換執(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 之所以稱為『 潛在 』暫停點,而不是暫停點,是因為并不是所有的 await 都會暫停,只有遇到類似 IO、手動起子線程等情況時才會暫停當前調(diào)用棧的運行。

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

    參考資料

    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)盤了,歡迎下載!

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

    在看點這里好文分享給更多人↓↓

    瀏覽 50
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    97色色婷婷 | 翔田千里无码AV在线观看 | 免费的黄色的视频 | IPX-811桃乃木かな无码破解 | 天天干天天日天天草 |