Swift 中的 Task

Swift 中的 Task 是 WWDC 2021 引入的并發(fā)框架的一部分。任務允許我們從非并發(fā)方法創(chuàng)建并發(fā)環(huán)境,使用 async/await 調(diào)用方法。

第一次處理任務時,您可能會認識到調(diào)度隊列(dispatch queue)和任務(tasks)之間的相識程度。兩者都允許在具有特定優(yōu)先級的不同線程上分派工作。然而,任務通過消除冗長的調(diào)度隊列代碼,使我們的生活變得相當不同且更輕松。

您可以在我的文章 Swift 中的async/await了解有關(guān) async/await 的更多信息。

如何創(chuàng)建然后運行一個 Task

在 Swift 中創(chuàng)建一個basicTask如下所示:

let basicTask = Task {
    return "This is the result of the task"
}

如您所見,我們保留了對返回字符串值的 basicTask 的引用。我們可以使用引用來讀出結(jié)果值:

let basicTask = Task {
    return "This is the result of the task"
}
print(await basicTask.value)
// Prints: This is the result of the task

此示例返回一個字符串,但也可能引發(fā)錯誤:

let basicTask = Task {
    // .. 做一些工作 ..
    throw ExampleError.somethingIsWrong
}

do {
    print(try await basicTask.value)
} catch {
    print("Basic task failed with error: \(error)")
}

// Prints: Basic task failed with error: somethingIsWrong

換句話說,您可以使用任務來產(chǎn)生錯誤

如何運行任務

好吧,上面的例子已經(jīng)給出了本節(jié)的答案。任務在創(chuàng)建后會立即運行,不需要顯式啟動。重要的是要了解需要執(zhí)行的工作是在任務創(chuàng)建后直接執(zhí)行的,因為它告訴您僅在允許任務內(nèi)工作開始時才會創(chuàng)建它。

在任務中執(zhí)行異步方法

除了同步返回值或拋出錯誤外,任務還可以執(zhí)行異步方法。我們需要一個任務來在不支持并發(fā)的函數(shù)中執(zhí)行任何異步方法。您可能已經(jīng)熟悉以下錯誤:

'async' call in a function that does not support concurrency is a common error in Swift.

不支持并發(fā)的函數(shù)中的“async”調(diào)用是 Swift 中的常見錯誤。

在此示例中,executeTask 方法是另一個任務的簡單包裝器:

func executeTask() async {
    let basicTask = Task {
        return "This is the result of the task"
    }
    print(await basicTask.value)
}

我們可以通過在一個新任務中調(diào)用executeTask()方法來解決上述錯誤。

var body: some View {
    Text("Hello, world!")
        .padding()
        .onAppear {
            Task {
                await executeTask()
            }
        }
}

func executeTask() async {
    let basicTask = Task {
        return "This is the result of the task"
    }
    print(await basicTask.value)
}

該任務創(chuàng)建了一個并發(fā)支持環(huán)境,我們可以在其中調(diào)用異步方法 executeTask()。有趣的是,即使我們沒有在 onappear 方法中保留對已創(chuàng)建任務的引用,我們的代碼也會執(zhí)行,這里來到我下一節(jié)要說明的內(nèi)容:取消任務。

處理取消

在想到處理任務取消時,您可能會驚訝地看到您的任務正在執(zhí)行,即使您沒有保留對它的引用。 Combine 中的發(fā)布者訂閱要求我們保持強引用以確保發(fā)出值。與 Combine 相比,您可能希望在釋放所有引用后也取消任務。

但是,Task的工作方式不同,因為無論您是否保留引用,它們都會運行。保留引用的唯一原因是讓自己能夠等待結(jié)果或取消任務。

取消一個任務

為了向您解釋任務取消是如何工作的,我們將使用一個加載圖像的新代碼示例:

struct ContentView: View {
    @State var image: UIImage?

    var body: some View {
        VStack {
            if let image = image {
                Image(uiImage: image)
            } else {
                Text("Loading...")
            }
        }.onAppear {
            Task {
                do {
                    image = try await fetchImage()
                } catch {
                    print("Image loading failed: \(error)")
                }
            }
        }
    }

    func fetchImage() async throws -> UIImage? {
        let imageTask = Task { () -> UIImage? in
            let imageURL = URL(string: "https://source.unsplash.com/random")!
            print("Starting network request...")
            let (imageData, _) = try await URLSession.shared.data(from: imageURL)
            return UIImage(data: imageData)
        }
        return try await imageTask.value
    }
}

上面的代碼例子獲取了一張隨機的圖片,如果請求成功,就會相應地顯示出來。

為了這個演示,我們可以在imageTask創(chuàng)建后立即取消它:

func fetchImage() async throws -> UIImage? {
    let imageTask = Task { () -> UIImage? in
        let imageURL = URL(string: "https://source.unsplash.com/random")!
        print("Starting network request...")
        let (imageData, _) = try await URLSession.shared.data(from: imageURL)
        return UIImage(data: imageData)
    }
    // Cancel the image request right away:
    imageTask.cancel()
    return try await imageTask.value
}

上面的取消調(diào)用將會阻止請求,因為 URLSession 實現(xiàn)在執(zhí)行之前會執(zhí)行取消檢查。因此,上面的代碼示例打印出以下內(nèi)容:

Starting network request...
Image loading failed: Error Domain=NSURLErrorDomain Code=-999 "cancelled"

如您所見,我們的打印語句仍在執(zhí)行。這個打印語句是演示了如何使用靜態(tài)取消檢查的兩種方法的其中一種。另一種是通過在檢測到取消時拋出錯誤來停止執(zhí)行當前任務:

let imageTask = Task { () -> UIImage? in
    let imageURL = URL(string: "https://source.unsplash.com/random")!

    /// 如果任務已被取消,則拋出錯誤。
    try Task.checkCancellation()

    print("Starting network request...")
    let (imageData, _) = try await URLSession.shared.data(from: imageURL)
    return UIImage(data: imageData)
}
// Cancel the image request right away:
imageTask.cancel()

上面的代碼打印結(jié)果為:

Image loading failed: CancellationError()

如您所見,我們的打印語句和網(wǎng)絡請求都沒有被調(diào)用。

我們可以使用的第二種方法給我們一個取消的狀態(tài)。通過使用這種方法,我們允許自己在取消時執(zhí)行任何額外的清理工作:

let imageTask = Task { () -> UIImage? in
    let imageURL = URL(string: "https://source.unsplash.com/random")!

    guard Task.isCancelled == false else {
        // Perform clean up
        print("Image request was cancelled")
        return nil
    }

    print("Starting network request...")
    let (imageData, _) = try await URLSession.shared.data(from: imageURL)
    return UIImage(data: imageData)
}
// Cancel the image request right away:
imageTask.cancel()

在這種情況下,我們的代碼只打印出取消聲明。

執(zhí)行定期取消檢查對于防止您的代碼做不必要的工作至關(guān)重要。想象一個例子,我們將轉(zhuǎn)換返回的圖像;我們可能應該在整個代碼中添加多個檢查:

let imageTask = Task { () -> UIImage? in
    let imageURL = URL(string: "https://source.unsplash.com/random")!

    // 在網(wǎng)絡請求之前檢查取消。
    try Task.checkCancellation()
    print("Starting network request...")
    let (imageData, _) = try await URLSession.shared.data(from: imageURL)

    // 網(wǎng)絡請求后檢查取消,以防止開始我們繁重的圖像操作。
    try Task.checkCancellation()

    let image = UIImage(data: imageData)

    // 由于任務未取消,因此執(zhí)行返回圖像操作。
    return image
}

在可以很容易的掌控任務的取消,這使得我們很容易犯錯誤和進行不必要的工作。在執(zhí)行任務時,請保持警惕,確保你的代碼定期檢查取消的狀態(tài)。

設置優(yōu)先級

每個任務都可以有它的優(yōu)先級。我們可以應用的值類似于我們在使用調(diào)度隊列時可以配置的服務質(zhì)量級別。低、中、高優(yōu)先級看起來與操作設置的優(yōu)先級相似。

我們可以通過設置優(yōu)先級來管理任務執(zhí)行的順序

每個優(yōu)先級都有其目的,并且可以表明一項工作比其他工作更重要。但是不能保證您的任務一定更早執(zhí)行。例如,較低優(yōu)先級的作業(yè)可能已經(jīng)在運行。

配置優(yōu)先級有助于防止低優(yōu)先級任務比更高優(yōu)先級的任務更先執(zhí)行。

用于執(zhí)行的線程

默認情況下,一個任務在一個自動管理的后臺線程上執(zhí)行。通過測試,我發(fā)現(xiàn)默認的優(yōu)先級是25。打印出高優(yōu)先級的原始值,顯示其實相匹配的:

(lldb) p Task.currentPriority
(TaskPriority) $R0 = (rawValue = 25)
(lldb) po TaskPriority.high.rawValue
25

您可以設置斷點來驗證您的方法在哪個線程上運行:

通過使用斷點,您可以檢查任務正在運行的線程。

繼續(xù)您的 Swift 并發(fā)之旅

并發(fā)更改不僅僅是async-await,還包括許多您可以在代碼中受益的新功能?,F(xiàn)在您已經(jīng)了解了任務的基礎知識,是時候深入了解其他新的并發(fā)特性了:

結(jié)論

Swift 中的Task允許我們創(chuàng)建一個并發(fā)環(huán)境來運行異步方法。取消任務需要明確的檢查,以確保我們不去執(zhí)行任何不必要的工作。通過配置我們?nèi)蝿盏膬?yōu)先級,我們可以管理執(zhí)行的順序。

轉(zhuǎn)自 Tasks in Swift explained with code examples

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容