WWDC21 Explore structured concurrency in Swift

探索Swift中的結構化併發編程

當你有代碼須要與其餘代碼同時運行時,選擇合適的工具來完成任務是很重要的。咱們將帶你瞭解你能夠在Swift中建立的不一樣類型的併發任務,告訴你如何建立任務組,並找出如何取消正在進行的任務。咱們還將提供指導,說明何時你可能想要使用非結構化的任務。爲了得到本節課的最大收穫,咱們首先建議觀看 "在Swift中認識async/await"。數組

概述

Swift 5.5 引入了一種編寫併發程序的新方法,使用了一個叫作結構化併發的概念。結構化併發背後的想法是基於結構化編程的,它是如此直觀,以致於你不多去想它,但思考它將幫助你理解結構化併發。緩存

所以,讓咱們深刻了解一下。安全

wwdc2021-10134_hd-0036.png

在計算機的早期,程序很難閱讀,由於它們被寫成了一連串的指令,控制流被容許處處跳躍。今天,你不會看到這種狀況,由於語言使用結構化編程,使控制流更加統一。 例如,if-then語句使用結構化的控制流。服務器

wwdc2021-10134_hd-0039.png

它規定了一個嵌套的代碼塊在從上到下移動的過程當中只能有條件地執行。在Swift中,該塊也準守靜態範圍,這意味着只有當名字在一個封閉的塊中被定義時纔是可見的。這也意味着,在一個區塊中定義的任何變量的生命週期將在離開區塊時結束。所以,帶有靜態範圍的結構化編程使得控制流和變量生命週期變得容易理解。markdown

wwdc2021-10134_hd-0040.png

更普遍地說,結構化的控制流能夠天然地進行排序和嵌套。這可讓你從上到下閱讀你的整個程序。因此,這些就是結構化編程的基本原理。你能夠想象,這很容易被認爲是理所固然的,由於它對咱們今天來講是如此直觀。可是,今天的程序以異步和併發代碼爲特徵,而它們卻沒有可以利用結構化編程來使這些代碼更容易編寫。網絡

使用結構化併發的例子

首先,讓咱們考慮一下結構化編程如何使異步代碼更簡單。多線程

wwdc2021-10134_hd-0041.png

假設你須要從互聯網上獲取一堆圖片,並按順序調整它們的大小,使之成爲縮略圖。這段代碼以異步方式完成這項工做,接收一個標識圖片的字符串集合。你會注意到這個函數在調用時沒有返回一個值。這是由於該函數將其結果或錯誤傳給了一個完成處理程序。這種模式容許調用者在稍後的時間收到一個答案。做爲這種模式的結果,這個函數不能使用結構化的控制流來處理錯誤。這是由於只有在處理從一個函數中拋出的錯誤時纔有意義,而不是在一個函數中。此外,這個模式還阻止你使用循環來處理每一個縮略圖。遞歸是必須的,由於函數完成後運行的代碼必須嵌套在處理程序中。如今,讓咱們來看看之前的代碼,但要重寫成使用新的async/await語法,它是基於結構化編程的。閉包

wwdc2021-10134_hd-0042.png

我放棄了函數中的完成處理程序參數。取而代之的是,在它的類型簽名中用 "async "和 "throws "來註解。它還返回一個值,而不是什麼都沒有。併發

wwdc2021-10134_hd-0043.png

在函數的主體中,我使用 "await "來表示一個異步動做的發生,而且在該動做以後運行的代碼不須要嵌套。

wwdc2021-10134_hd-0044.png

這意味着我如今能夠在縮略圖上循環,按順序處理它們。

wwdc2021-10134_hd-0045.png

我還能夠拋出和捕獲錯誤,編譯器會檢查我是否忘記了。若是想深刻了解async/await,請查看 "在Swift中認識async/await "這一環節。

那麼,這段代碼很好,但若是你要爲成千上萬的圖片製做縮略圖呢?一次處理一個縮略圖再也不是理想的作法。另外,若是每一個縮略圖的尺寸必須從另外一個URL下載,而不是固定的尺寸,怎麼辦?如今有機會增長一些併發性,因此多個下載能夠並行進行。你能夠建立額外的任務來給程序添加併發性。

Tasks in Swift

任務是Swift的一項新功能,與異步函數攜手合做。

wwdc2021-10134_hd-0046.png

任務爲運行異步代碼提供了一個新的執行環境。每一個任務相對於其餘執行上下文都是併發運行的。在安全有效的狀況下,它們會被自動安排爲並行運行。

因爲任務被深度整合到Swift中,編譯器能夠幫助防止一些併發性錯誤。

另外,請記住,調用一個異步函數並不會爲該調用建立一個新任務。你要明確地建立任務。

Swift 中有幾種不一樣的任務,由於結構化併發是關於靈活性和簡單性之間的平衡。所以,在本次會議的其他部分,咱們將介紹和討論每一種任務,以幫助你理解它們的權衡。

結構化任務

Async-let tasks

讓咱們從這些任務中最簡單的開始,它是用一種叫作async-let綁定的新語法形式建立的。

wwdc2021-10134_hd-0047.png

爲了幫助你理解這種新的語法形式,我想首先分解一下普通let綁定的評估過程。有兩個部分:等號右邊的初始化表達式和左邊的變量名稱。在let以前或以後可能還有其餘語句,因此我在這裏也會包括這些。

wwdc2021-10134_hd-0050.png

一旦Swift到達一個let綁定,它的初始化器將被評估以產生一個值。在這個例子中,這意味着從一個URL下載數據,這可能須要一段時間。數據下載完畢後,Swift將把這個值綁定到變量名上,而後再進行後面的語句。請注意,這裏只有一個執行流程,正如經過每一個步驟的箭頭所追蹤的那樣。

因爲下載可能須要一段時間,你但願程序開始下載數據,並繼續作其餘工做,直到真正須要這些數據。 爲了實現這一點,你能夠在現有的let綁定前面加上async這個詞。

wwdc2021-10134_hd-0051.png

這就把它變成了一個叫作async-let的併發綁定。

併發綁定的評估與順序綁定是徹底不一樣的,因此讓咱們來學習它是如何工做的。 我將從遇到綁定以前的那一刻開始。

wwdc2021-10134_hd-0052.png

爲了評估一個併發綁定,Swift首先會建立一個新的子任務,這是建立它的那個任務的子任務。由於每一個任務都表明了你的程序的執行環境,因此在這一步中會同時出現兩個箭頭。 第一個箭頭是子任務的,它將當即開始下載數據。 第二個箭頭是針對父任務的,它將當即把變量結果與一個佔位符值綁定。 這個父任務就是正在執行前面語句的那個任務。當數據被子任務併發下載時,父任務繼續執行併發綁定後的語句。 可是在到達一個須要實際結果值的表達式時,父任務將等待子任務的完成,子任務將履行結果的佔位符。

在這個例子中,咱們對URLSession的調用也可能拋出一個錯誤。這意味着等待結果可能會給咱們一個錯誤。因此我須要寫 "try "來處理它。不要擔憂。再次讀取結果的值不會從新計算其值。

應用示例

如今你已經看到了async-let是如何工做的,你能夠用它來爲縮略圖的獲取代碼添加併發性。我已經將以前的一段獲取單張圖片的代碼分解到本身的函數中。

wwdc2021-10134_hd-0053.png

這裏的這個新函數也是從兩個不一樣的URL中下載數據:一個是全尺寸的圖片自己,另外一個是元數據,其中包含了最佳的縮略圖尺寸。

請注意,在順序綁定的狀況下,你在let的右邊寫上 "try await",由於那是觀察錯誤或暫停的地方。

wwdc2021-10134_hd-0054.png

爲了使兩個下載同時發生,你在這兩個let的前面寫上 "async"。

因爲下載如今發生在子任務中,你再也不在併發綁定的右邊寫 "try await"。

wwdc2021-10134_hd-0055.png

這些效果只有在使用被併發綁定的變量時纔會被父任務觀察到。因此你在表達式讀取元數據和圖像數據以前寫 "try await"。

另外,注意到使用這些被併發綁定的變量不須要方法調用或其餘任何改變。這些變量的類型與它們在順序綁定中的類型相同。

任務樹結構

如今,我一直在談論的這些子任務其實是一個叫作任務樹的層次結構的一部分。這個樹不只僅是一個實現細節。它是結構化併發的一個重要部分。它影響着你的任務的屬性,如取消、優先級和任務本地變量。每當你從一個異步函數調用到另外一個異步函數時,同一個任務被用來執行調用。因此,函數fetchOneThumbnail繼承了該任務的全部屬性。當建立一個新的結構化任務時,好比用async-let,它就會成爲當前函數所運行的任務的子任務。任務不是特定函數的子任務,但它們的生命週期多是以它爲範圍的。

wwdc2021-10134_hd-0056.png

樹是由每一個父任務和其子任務之間的連接組成的。連接強制執行一條規則,即父任務只有在其全部的子任務都完成後才能完成其工做。

這條規則甚至在控制流異常的狀況下也有效,由於控制流會阻止子任務被等待。

wwdc2021-10134_hd-0057.png

例如,在這段代碼中,我首先在圖像數據任務以前等待元數據任務。若是第一個等待的任務以拋出錯誤的方式結束,fetchOneThumbnail函數必須當即經過拋出錯誤退出。但執行第二個下載的任務會發生什麼?在非正常退出過程當中,Swift會自動將未等待的任務標記爲取消,而後等待它完成,再退出函數。將一個任務標記爲取消並不會中止該任務。它只是通知該任務再也不須要其結果。

wwdc2021-10134_hd-0058.png

事實上,當一個任務被取消時,全部做爲該任務後裔的子任務也將被自動取消。

所以,若是URLSession的實現建立了本身的結構化任務來下載圖片,這些任務將被標記爲取消。

wwdc2021-10134_hd-0059.png

一旦它直接或間接建立的全部結構化任務都完成了,函數 fetchOneThumbnail 就會經過拋出錯誤而最終退出。 這種保證是結構化併發的基礎。

它經過幫助你管理任務的生命週期來防止你意外地泄露任務,就像ARC自動管理內存的壽命同樣。

到目前爲止,我已經給了你一個關於取消如何傳播的概述。

wwdc2021-10134_hd-0060.png

但任務最終什麼時候中止呢?若是任務正處於一個重要的事務中,或者有開放的網絡鏈接,直接中止任務是不正確的。

這就是爲何Swift中的任務取消是合做性的。

你的代碼必須明確地檢查取消,並以任何適當的方式結束執行。

你能夠從任何函數中檢查當前任務的取消狀態,不管它是不是異步的。

這意味着你在實現你的API時應該考慮到取消的問題,特別是當它們涉及到長期運行的計算時。

你的用戶可能會從一個能夠取消的任務中調用你的代碼,他們會但願計算能儘快中止。

爲了看看使用合做取消有多簡單,讓咱們回到縮略圖獲取的例子。

wwdc2021-10134_hd-0062.png

在這裏,我重寫了原來的函數,該函數被賦予全部要獲取的縮略圖,所以它使用fetchOneThumbnail函數來代替。

若是這個函數是在一個被取消的任務中調用的,咱們不但願由於建立無用的縮略圖而耽誤咱們的應用程序。

因此我能夠在每一個循環迭代的開始添加一個對checkCancellation的調用。

這個調用只有在當前任務被取消時纔會拋出一個錯誤。

wwdc2021-10134_hd-0063.png

你也能夠把當前任務的取消狀態做爲一個布爾值來獲取,若是這對你的代碼更合適的話。

注意,在這個版本的函數中,我返回了一個部分結果,一個只有部分請求的縮略圖的字典。

當這樣作時,你必須確保你的API清楚地說明能夠返回部分結果。

不然,任務取消可能會給你的用戶帶來致命的錯誤,由於他們的代碼須要一個完整的結果,即便是在取消的過程當中。

到目前爲止,你已經看到async-let提供了一種輕量級的語法,用於在你的程序中添加併發性,同時抓住告終構化編程的本質

Group tasks

我想告訴你的下一種任務被稱爲組任務。它們提供了比async-let更多的靈活性,同時又不放棄結構化併發的全部美好特性。正如咱們前面所看到的,當有固定的併發量時,async-let工做得很好。讓咱們考慮一下我前面討論的兩個函數。

wwdc2021-10134_hd-0064.png

對於循環中的每一個縮略圖ID,咱們調用fetchOneThumbnail來處理它,這正好創造了兩個子任務。即便咱們將該函數的主體內聯到這個循環中,併發量也不會改變。Async-let的做用域就像一個變量綁定。這意味着這兩個子任務必須在下一個循環迭代開始以前完成。可是,若是咱們想讓這個循環啓動任務來同時獲取全部的縮略圖呢?那麼,併發量就不是靜態的了,由於它取決於數組中ID的數量。 對於這種狀況,正確的工具是任務組。

任務組是一種結構化併發的形式,旨在提供一個動態的併發量。

wwdc2021-10134_hd-0065.png

你能夠經過調用withThrowingTaskGroup函數來引入一個任務組。這個函數給你一個範圍內的組對象來建立容許拋出錯誤的子任務。

添加到組中的任務不能超過定義該組的塊的範圍。

因爲我已經把整個for-loop放在塊內,我如今可使用組來建立動態的任務數量。

wwdc2021-10134_hd-0066.png

你能夠經過調用組的異步方法來建立組中的子任務。

一旦被添加到一個組中,子任務就會當即開始執行,而且以任何順序執行。

wwdc2021-10134_hd-0067.png

當組對象超出範圍時,組內全部任務的完成將被隱含地等待。

這是我前面描述的任務樹規則的一個結果,由於組任務也是有結構的。

在這一點上,咱們已經實現了咱們想要的併發性:每次調用fetchOneThumbnail都有一個任務,它自己將使用async-let建立另外兩個任務。這是結構化併發的另外一個不錯的屬性。

你能夠在組任務中使用async-let,或者在async-let任務中建立任務組,而樹中的併發層次天然地組成了。

wwdc2021-10134_hd-0068.png

如今,這段代碼尚未徹底準備好運行。若是咱們試圖運行它,編譯器會頗有幫助地提醒咱們有一個數據競爭問題。

問題是,咱們試圖從每一個子任務中插入一個縮略圖到一個字典中。當增長程序中的併發量時,這是一個常見的錯誤。數據競爭就會意外地產生。

這個字典不能同時處理一個以上的訪問,若是兩個子任務試圖同時插入縮略圖,這可能會致使崩潰或數據損壞。

wwdc2021-10134_hd-0069.png

在過去,你必須本身調查這些bug,但Swift提供了靜態檢查,以防止這些bug首先發生。每當你建立一個新的任務時,該任務執行的工做都在一個新的閉包類型中,稱爲**@Sendable**閉包。

@Sendable閉包的主體被限制在其詞法上下文中捕獲可變的變量,由於這些變量在任務啓動後可能被修改。這意味着你在任務中捕獲的值必須是安全的,能夠共享。

例如,由於它們是值類型,如IntString,或者由於它們是旨在從多個線程訪問的對象,如actors,以及實現本身同步的類。

咱們有一節課專門討論這個話題,叫作 "用Swift actors保護易變狀態",因此我鼓勵你去看看。

wwdc2021-10134_hd-0070.png

爲了不咱們例子中的數據競爭,你可讓每一個子任務返回一個值。這種設計讓父任務單獨負責處理結果。 在這個例子中,我指定每一個子任務必須返回一個包含縮略圖的字符串IDUIImage的元組。而後,在每一個子任務中,我讓它們返回鍵值元組供父任務處理,而不是直接寫到字典中。

wwdc2021-10134_hd-0072.png

父任務可使用新的 for-await 循環來迭代每一個子任務的結果。for-await 循環按照完成的順序從子任務中獲取結果。由於這個循環按順序運行,父任務能夠安全地將每一個鍵值對添加到字典中。

這只是使用 for-await 循環來訪問一個異步值序列的一個例子。

若是你本身的類型符合 AsyncSequence 協議,那麼你也可使用 for-await 來迭代它們。

你能夠在 "認識AsyncSequence "環節中瞭解更多。

雖然任務組是結構化併發的一種形式,但在任務樹規則的實現方式上,組任務與async-let任務有一個小小的區別。

假設在遍歷這個組的結果時,我遇到了一個完成時有錯誤的子任務。由於這個錯誤被拋出了組的塊,而後組中的全部任務將被隱式取消,而後等待。

這就像async-let同樣工做。

不一樣的是,當你的組經過正常退出塊而超出範圍時。那麼,取消就不是隱式的了。

這種行爲使你更容易使用任務組來表達fork-join模式,由於**(jobs)任務**只會被等待,不會被取消。

你也能夠在退出塊以前使用組的cancelAll方法手動取消全部任務。

請記住,不管你如何取消一個任務,取消都會自動向樹上傳播。

Async-letGroup tasksSwift中提供範圍結構化任務的兩種任務。

非結構化任務

Unstructured tasks

以前向你展現告終構化併發是如何簡化錯誤傳播、取消和其餘處理工做的,當你向一個有明確層次的任務的程序中添加併發時。但咱們知道,當你在程序中添加任務時,你並不老是有一個層次結構。

Swift也提供了非結構化的任務API,這讓你有更多的靈活性,代價是須要更多的人工管理。

wwdc2021-10134_hd-0073.png

有不少狀況下,一個任務可能不屬於一個明確的層次結構。

最明顯的是,若是你想啓動一個任務來作非同步代碼的異步計算,你可能根本就沒有一個父任務。

另外,你想要的任務的生命週期可能不適合單個範圍甚至單個函數的限制。

例如,你可能想在一個將對象放入活動狀態的方法調用中啓動一個任務,而後在另外一個將對象停用的方法調用中取消其執行。

AppKitUIKit中實現委託對象時,這種狀況常常出現。

wwdc2021-10134_hd-0074.png

UI工做必須發生在主線程上,正如Swift actors會議所討論的,Swift經過聲明屬於MainActorUI類來確保這一點。

wwdc2021-10134_hd-0075.png

假設咱們有一個集合視圖,而咱們還不能使用集合視圖的數據源API。相反,咱們想使用咱們剛剛寫的fetchThumbnails函數,在集合視圖中的項目顯示時從網絡上抓取縮略圖。

然而,委託方法不是異步的,因此咱們不能只是等待對一個異步函數的調用。

咱們須要爲此啓動一個任務,但這個任務其實是咱們爲響應委託動做而啓動的工做的延伸。咱們但願這個新任務仍然以UI優先級在主角色上運行。咱們只是不想把任務的生命週期限制在這個單一委託方法的範圍內。

對於這樣的狀況,Swift容許咱們構建一個非結構化的任務。

wwdc2021-10134_hd-0076.png

讓咱們把代碼的異步部分移到一個閉包中,並經過該閉包來構造一個異步任務。

如今是在運行時發生的狀況。

wwdc2021-10134_hd-0077.png

當咱們到達建立任務的點時,Swift 會安排它在與源做用域相同的行爲體上運行,在這種狀況下,它就是主行爲體。

wwdc2021-10134_hd-0078.png

同時,控制權會當即返回給調用者。縮略圖任務將在主線程上運行,而不會當即阻塞委託方法上的主線程。

以這種方式構造任務給了咱們一個介於結構化和非結構化代碼之間的中間點。

wwdc2021-10134_hd-0079.png

一個直接構建的任務仍然繼承了它所啓動的上下文的Actor(若是有的話),它也繼承了原任務的優先級和其餘特徵,就像一個組任務或一個async-let那樣。

然而,新任務是無範圍的。它的生命週期不受它被啓動的範圍的約束。

原點甚至不須要是異步的。咱們能夠在任何地方建立一個無範圍的任務。

爲了換取全部這些靈活性,咱們還必須手動管理那些結構化併發會自動處理的事情。

取消和錯誤不會自動傳播,任務的結果也不會被隱式地等待,除非咱們採起顯式的行動來這樣作。

因此咱們啓動了一個任務,在顯示集合視圖項目時獲取縮略圖,若是該項目在縮略圖準備好以前被滾動出視圖,咱們也應該取消該任務。因爲咱們使用的是一個無範圍的任務,因此這個取消不是自動的。

如今讓咱們來實現它。

wwdc2021-10134_hd-0081.png

在咱們構建任務以後,讓咱們保存咱們獲得的值。當咱們建立任務時,咱們能夠把這個值放入一個以行索引爲鍵的字典中,這樣咱們之後就能夠用它來取消這個任務。一旦任務完成,咱們也應該把它從字典中刪除,這樣咱們就不會在任務已經完成的狀況下試圖取消它。

注意這裏,咱們能夠在那個異步任務的內部和外部訪問同一個 dictionary,而不會被編譯器標記爲數據競爭。

咱們的委託類被綁定到主角色上,而新任務則繼承了主角色,因此它們永遠不會一塊兒並行運行。

咱們能夠安全地訪問這個任務中與主角色綁定的類的存儲屬性,而不用擔憂數據競爭。

wwdc2021-10134_hd-0082.png

同時,若是咱們的委託人後來被告知同一錶行已經從顯示中移除,那麼咱們能夠調用該值的取消方法來取消該任務。

Detached tasks

因此如今咱們已經看到了咱們如何建立獨立於做用域運行的非結構化任務,同時仍然繼承了該任務的起始上下文的特性。

但有時你並不想從你的起始上下文中繼承任何東西。

爲了得到最大的靈活性,Swift提供了分離式任務。

wwdc2021-10134_hd-0083.png

就像名字所暗示的那樣,分離的任務是獨立於其上下文的。

它們仍然是非結構化的任務。

它們的生命期不受其起始做用域的約束。

但分離的任務也不會從它們的起始做用域中獲取任何其餘東西。

默認狀況下,它們不被限制在同一個角色上,也不須要在與它們被啓動的地方相同的優先級上運行。

分離的任務是獨立運行的,在優先級等方面有通用的默認值,但它們也能夠用可選的參數來控制新任務的執行方式和位置。

wwdc2021-10134_hd-0084.png

比方說,當咱們從服務器上獲取縮略圖後,咱們想把它們寫入本地磁盤緩存,這樣若是咱們之後試圖獲取它們就不會再碰到網絡。

緩存不須要發生在主角色上,即便咱們取消了對全部縮略圖的獲取,對咱們獲取的縮略圖進行緩存仍然是有幫助的。

所以,讓咱們經過使用一個分離的任務來啓動緩存。

當咱們分離一個任務時,咱們在設置新任務的執行方式上也有了更大的靈活性。

緩存應該發生在一個較低的優先級上,不會干擾主用戶界面,咱們能夠在分離這個新任務時指定後臺優先級。

wwdc2021-10134_hd-0085.png

如今讓咱們提早計劃一下。若是咱們有多個後臺任務要在咱們的縮略圖上執行,咱們未來應該怎麼作?咱們能夠分離出更多的後臺任務,但咱們也能夠在分離出來的任務裏面利用結構化的併發性。咱們能夠將全部不一樣種類的任務結合在一塊兒,利用它們各自的優點。咱們能夠設置一個任務組,並將每一個後臺任務做爲子任務生成到該組中,而不是爲每一個後臺任務分離出一個獨立的任務。這樣作有不少好處。

wwdc2021-10134_hd-0086.png

若是咱們未來確實須要取消後臺任務,使用任務組意味着咱們能夠取消全部的子任務,只需取消頂層的分離任務。

這種取消將自動傳播到子任務中,咱們不須要跟蹤一個處理數組。此外,子任務會自動繼承其父任務的優先級。

爲了保持全部這些工做在後臺進行,咱們只須要將分離的任務放在後臺,這將自動傳播到它的全部子任務,因此咱們不須要擔憂忘記過渡地設置後臺優先級而意外地餓死了UI工做。

總結

在這一點上,咱們已經看到了Swift中全部主要的任務形式。

wwdc2021-10134_hd-0087.png

Async-let容許將固定數量的子任務做爲變量綁定來生成,若是綁定超出了範圍,則自動管理取消和錯誤傳播。

當咱們須要一個動態數量的子任務,而且仍然被綁定在一個範圍內時,咱們能夠上移到任務組。

若是咱們須要把一些範圍不大,但仍與原任務有關的工做分開,咱們能夠構建非結構化的任務,但咱們須要手動管理這些任務。

爲了得到最大的靈活性,咱們還有分離的任務,這是手動管理的任務,不從其起源處繼承任何東西。

任務和結構化併發只是Swift支持的併發功能套件中的一部分。

請務必查看全部這些其餘的精彩講座,看看它是如何與語言的其餘部分結合起來的。

"Meet async/await in Swift "爲你提供了更多關於異步函數的細節,它爲咱們編寫併發代碼提供告終構化的基礎。

Actor提供了數據隔離,以建立避免數據競爭的併發系統。請參閱 "Protect mutable state with Swift actors "一節,以瞭解更多信息。 咱們看到任務組上的 "for await "循環,這些只是AsyncSequence的一個例子,它爲處理異步數據流提供了一個標準接口。 "Meet AsyncSequence "環節更深刻地探討了處理序列的可用API。

任務與核心操做系統集成,以實現低開銷和高可擴展性,而 "Swift concurrency: Behind the scenes"給出了更多關於如何實現這一目標的技術細節。

全部這些功能結合在一塊兒,使在Swift中編寫併發代碼變得簡單而安全,讓你在編寫代碼時可以最大限度地利用你的設備,同時仍然專一於你的應用程序的有趣部分,少考慮管理併發任務的機制或由多線程引發的潛在錯誤的擔心。

相關文章
相關標籤/搜索