TaskGroup Swift

by Quang Huy
409 views

Trong sự kiện WWDC21, apple đã giới thiệu với công chúng swift 5.5 với nhiều cải tiến, trong đó có async/await. Đây là một cập nhật lớn về cách chúng ta làm việc với bất đồng bộ, chúng dùng để viết những đoạn code async dễ hiểu và dễ đọc.
Tuy nhiên, async/await bản thân nó không cho phép chúng ta chạy tất cả mọi thứ một cách đồng thời, ngay cả khi với nhiều CPU cores cùng hoạt động, async/ await code vẫn sẽ thực thi tuần tự.
Để giải quyết vấn đề này, vẫn ở swift 5.5, apple giới thiệu với chúng ta Task và TaskGroups. Chúng là 1 trong những phần quan trọng trong concurrency framework của Swift.

   

1-Bản chất taskgroup

Đúng như tên gọi, TaskGroup là 1 tập hợp những task con thực thi đồng thời, nói cách khác, TaskGroup sẽ giúp chúng ta chia 1 công việc thành nhiều concurrent operations.
Taskgroup hoạt động tốt nhất khi các task con của nó trả về cùng 1 kiểu data, tuy nhiên, ta cũng có thể ép chúng hỗ trợ kiểu data khác nhau.
Để dể hiểu hơn, mình có 5 gạch đầu dòng về Taskgroup:

– Một taskgroup là 1 tập hợp các task async mà trong tập hợp đó, các task này hoạt động độc lập với nhau.

– Tất cả các task con sẽ thực thi 1 cách đồng thời và gần như ngay lập tức sau khi được add vào Taskgroup.

– Chúng ta không thể kiểm soát khi nào các task con hoàn tất việc thực thi của chúng. Vì thế, chúng ta không nên sử dụng taskgroup nếu muốn các task con hoàn thành theo một thứ tự nào đó.

– Một taskgroup chỉ return khi và chỉ khi tất các task con của nó hoàn tất. Nói cách khác, tất cả các task của Taskgroup chỉ tồn tại trong chính TaskGroup đó còn đang hoạt động.

– Có thể dừng một taskgroup bằng cách return 1 giá trị, hoặc return void, hoặc throw error.

   

2-Mức độ ưu tiên

TaskGroup có thể được tạo với 1 trong 4 độ ưu tiên: High là độ ưu tiên cao nhất, tiếp đó là Medium, Low, và Background.
Mức độ ưu tiên task cho phép hệ thống sắp xếp task nào thực thi trước.
Nếu so sánh với các mức độ của DispatchQueue, userInitiated và utility sẽ là High và low. Taskgroup không có mức độ tương đương với userInteractive, vì với mức độ đó, nó dành riêng cho user interface.

   

3-Khởi tạo taskGroup

Để tạo một taskgroup, ta có thể sử dụng withTaskGroup(of:returning:body:) hoặc withThrowingTaskGroup(of:returning:body:). Ở bài viết này, mình không sử dụng taskgroup có throw error, nên nếu muốn tìm hiểu thêm về withThrowingTaskGroup(of:returning:body:), mọi người có thể xem thêm ở document của apple.

   

4-Làm việc với taskgroup

Mình có tạo ra 1 struct demoChildTask việc nhân 2 số với nhau, và trong đó có một khoảng nghỉ để tiện cho việc control taskgroup. Ở đây mình có tạo ra một mảng demoChildTask:

let demoOperations = [
    demoChildTask(name: "operation-0", a: 5, b: 1, sleepDuration: 5),
    demoChildTask(name: "operation-1", a: 14, b: 7, sleepDuration: 1),
    demoChildTask(name: "operation-2", a: 8, b: 2, sleepDuration: 3),
]

Sau đó add các task con vào taskgroup bằng cách chạy vòng lặp array demoChildTask

let demoResult = await withTaskGroup(of: (String, Double).self,
                                         returning: [String: Double].self,
                                         body: { taskGroup in
        
        // Loop through demoOperations array
        for operation in demoOperations {
            
            // Add child task to task group
            taskGroup.addTask {
                
                // Execute slow operation
                let value = await operation.slowMulti()
                
                // Return child task result
                print("Quang Huy -: \(operation.name)")
                return (operation.name, value)
            }
            
        }
        
        // Collect child task result...
    })

Lưu ý, kiểu dữ liệu task con trả về phải đúng là kiểu dữ liệu của task con mà ta đã khai báo khi khởi tạo TaskGroup

Như đã đề cập, tất cả các task con đều thực thi đồng thời với nhau, nên ta không thể control việc khi nào chúng hoàn tất. Vì thế để nhận result của từng task con, ta phải loop qua taskgroup:

// Collect results of all child task in a dictionary
var demoChildTaskResults = [String: Double]()
for await result in taskGroup {
    print("Quang Huy 1 - \(result.0)")
    // Set operation name as key and operation result as value
    demoChildTaskResults[result.0] = result.1
}
        
// All child tasks finish running, return task group result
return demoChildTaskResults

Ở đoạn code trên, mình sử dụng await keyword, keyword này có ý nghĩa là vòng lặp có thể dừng lại để đợi task con thực thi xong. Mỗi khi task con xong, vòng lặp lại tiếp tục và update giá trị cho childTaskResults.
Sau khi xử lý result của tất cả các task con hoàn tất, ta return về result của taskgroup. Giống như task con, return type của taskgroup cũng cần phải trùng với kiểu return khi khởi tạo nó:

Khi chạy đoạn code trên, ta nhận được đoạn log như này:

Như có thể thấy, task group cần ~ 5s để hoàn thành, 5s cũng là khoảng nghỉ dài nhất mà mình khởi tạo.

   

5-Tổng kết

Với sự ra mắt của TaskGroup, việc quản lý và sử dụng các concurrent task chưa bao giờ đơn giản đến thế. Đồng thời cũng là sự kết hợp hoàn hảo với async/await, thứ cũng là 1 cập nhật lớn ở WWDC21.


REFERENCE

https://www.hackingwithswift.com/quick-start/concurrency/what-are-tasks-and-task-groups

https://developer.apple.com/documentation/swift/taskgroup

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.