理解Swift中的任务组

理解Swift中的任务组

hudson 译 原文

苹果在Swift 5.5中引入了任务组,作为Swift并发框架的重要组成部分之一。顾名思义,任务组是并发运行的子任务的集合,只有当其所有子任务完成执行时,它才会返回。

在本文中,我想向您展示如何创建任务组,将子任务添加到任务组,并从所有子任务中收集结果。有很多话题要讨论,所以让我们开始吧。

准备工作

像往常一样,我将使用一些示例代码来解释并帮助您了解在Swift中任务组是如何工作的。在进入任务组之前,让我们先定义一个操作结构,我们将在本文中使用它。

swift 复制代码
struct SlowDivideOperation {
    
    let name: String
    let a: Double
    let b: Double
    let sleepDuration: UInt64
    
    func execute() async -> Double {
        
        // Sleep for x seconds
        await Task.sleep(sleepDuration * 1_000_000_000)
        
        let value = a / b
        return value
    }
}

SlowDivideOperation是一个简单的结构,它包含一个execute()函数,该函数根据给定的数字执行除法操作。请注意,在除以数字之前,我故意让它睡眠一段时间,从而减慢执行速度。

这使我们能够很好地控制除法操作应该执行多长时间,。因此,我们可以轻松观察当多个SlowDivideOperation在任务组中同时运行时会发生什么。

理解了这一点,让我们直接进入任务组。

任务组行为

为了使我们能够正确使用任务组,我们需要注意一些任务组行为:

  • 任务组由相互独立的异步任务(子任务)的集合组成。

  • 添加到任务组的所有子任务都会自动并发地开始运行。

  • 我们无法控制子任务何时完成执行。因此,如果我们希望子任务按特定顺序完成,我们不应该使用任务组。

  • 任务组仅在其所有子任务完成执行时返回。换句话说,任务组的所有子任务只能在其任务组的范围内。

  • 任务组可以通过返回值、返回void(非值返回)或抛出错误来退出。

现在您已经了解了任务组的行为方式,是时候编写一些Swift代码了。

创建任务组

要创建任务组,我们可以使用Swift 5.5中引入的withTaskGroup(of:returning:body:)withThrowingTaskGroup(of:returning:body:)函数。由于我们要创建的任务组不会抛出任何错误,我们将在示例代码中使用withTaskGroup(of:returning:body:)变体。它被调用时采取以下形式:

在我们的示例中,我们将创建一个任务组,生成多个子任务,执行SlowDivideOperation并返回其名称和结果。当所有SlowDivideOperation完成后,任务组将收集其所有子任务结果,并返回一个由所有SlowDivideOperation名称和结果组成的字典。

考虑到这一点,我们可以像这样使用函数创建一个任务组:

swift 复制代码
let allResults = await withTaskGroup(of: (String, Double).self,
                                     returning: [String: Double].self,
                                     body: { taskGroup in
    
  // 我们可以在这里使用`taskGroup`来创建子任务。
    
})

请注意,任务组的实例作为主体闭包的参数提供给我们。在下一节中,我们将了解如何利用此任务组实例来生成多个并发运行的子任务。

任务组如何工作

假设我们有一个SlowDivideOperation数组,如下所示:

swift 复制代码
let operations = [
    SlowDivideOperation(name: "operation-0", a: 5, b: 1, sleepDuration: 5),
    SlowDivideOperation(name: "operation-1", a: 14, b: 7, sleepDuration: 1),
    SlowDivideOperation(name: "operation-2", a: 8, b: 2, sleepDuration: 3),
]

然后,我们可以通过循环operations数组将子任务添加到任务组中:

swift 复制代码
let allResults = await withTaskGroup(of: (String, Double).self,
                                     returning: [String: Double].self,
                                     body: { taskGroup in
    
    // Loop through operations array
    for operation in operations {
        
        // Add child task to task group
        taskGroup.addTask {
            
            // Execute slow operation
            let value = await operation.execute()
            
            // Return child task result
            return (operation.name, value)
        }
        
    }
    
    // Collect child task results here...

})

值得一提的是,我们在子任务操作闭包中返回StringDouble,与我们之前设置的子任务结果数据类型匹配。

如前所述,所有子任务都将同时运行,我们无法控制它们何时完成运行。为了收集每个子任务的结果,我们必须像这样循环任务组:

swift 复制代码
// Collect results of all child task in a dictionary
var childTaskResults = [String: Double]()
for await result in taskGroup {
    // Set operation name as key and operation result as value
    childTaskResults[result.0] = result.1
}

// Task group finish running & return task group result
return childTaskResults

请注意,我们在循环时使用等待关键字来指示for循环在等待子任务完成时可能会暂停。每当子任务返回时,for循环都会重复并更新childTaskResults字典。

一旦所有子任务完成执行,for循环将退出并继续返回任务组结果。正如您可能已经注意到的,childTaskResults的数据类型必须与我们之前设置的任务组结果类型匹配。

提示:

如果您有任务组或子任务不返回值,请使用Void.self作为结果类型。

让我们在触发任务组之前和之后添加一些打印语句,以检查最终结果和执行所有子任务所需的时间。以下是完整的示例代码:

swift 复制代码
let operations = [
    SlowDivideOperation(name: "operation-0", a: 5, b: 1, sleepDuration: 5),
    SlowDivideOperation(name: "operation-1", a: 14, b: 7, sleepDuration: 1),
    SlowDivideOperation(name: "operation-2", a: 8, b: 2, sleepDuration: 3),
]

Task {
    
    print("Task start   : \(Date())")
    
    let allResults = await withTaskGroup(of: (String, Double).self,
                                         returning: [String: Double].self,
                                         body: { taskGroup in
        
        // Loop through operations array
        for operation in operations {
            
            // Add child task to task group
            taskGroup.addTask {
                
                // Execute slow operation
                let value = await operation.execute()
                
                // Return child task result
                return (operation.name, value)
            }
            
        }
        
        // Collect results of all child task in a dictionary
        var childTaskResults = [String: Double]()
        for await result in taskGroup {
            // Set operation name as key and operation result as value
            childTaskResults[result.0] = result.1
        }
        
        // All child tasks finish running, thus task group result
        return childTaskResults
    })
    
    print("Task end     : \(Date())")
    print("allResults   : \(allResults)")
    
}

如果我们尝试执行上述代码,我们得到的输出将如下:

swift 复制代码
Task start   : 2021-10-23 05:53:15 +0000
Task end     : 2021-10-23 05:53:20 +0000
allResults   : ["operation-1": 2.0, "operation-2": 4.0, "operation-0": 5.0]

如您所见,整个任务组花了5秒才完成。事实上,5秒也等于所有SlowDivideOperation中最长的睡眠时间,这表明所有子任务实际上都同时运行。

如果您查看所有结果,您会注意到它包含所有子任务的结果。这进一步证明,任务组只有在其所有子任务完成运行时才会返回。这也表明,子任务只能存在于任务组的上下文中。

如果您想尝试本文中的示例代码,您可以在这里获取。

感谢您的阅读。👨🏻‍💻

相关推荐
m0_748238923 天前
webgis入门实战案例——智慧校园
开发语言·ios·swift
Swift社区4 天前
Excel 列名称转换问题 Swift 解答
开发语言·excel·swift
东坡肘子5 天前
肘子的 Swift 周报 #063|异种肾脏移植取得突破
swiftui·swift·apple
威化饼的一隅5 天前
【多模态】swift-3框架使用
人工智能·深度学习·大模型·swift·多模态
opentogether7 天前
Swift 的动态性
开发语言·ssh·swift
苍墨穹天8 天前
SWIFT基本使用
linux·swift
SchneeDuan8 天前
从源码分析swift GCD_DispatchGroup
ios·swift·源码分析·gcd
请叫我飞哥@10 天前
iOS在项目中设置 Dev、Staging 和 Prod 三个不同的环境
ios·xcode·swift
Cedric_Anik12 天前
iOS渲染概述
ui·ios·swift
hxx22112 天前
iOS swift开发系列--如何给swiftui内容视图添加背景图片显示
ios·swiftui·swift