Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Task Group
Written by chuotfx on November 8, 2021

Task & Task Group trong 10 phút – Swift 5.5

iOS & Swift

Contents

  • Chuẩn bị
  • Task
    • Cú pháp
    • Áp dụng
  • Task priority
  • Static methods
  • Task Group
    • Cú pháp
    • Áp dụng
    • With Error
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Chúng ta tiếp tục tìm hiểu về New Concurrency trong Swift 5.5 và đối tượng lần này là Task & Task Group. Cũng là khái niệm tiếp theo cần khám phá trong Structured Concurrency. Mục đích chính là vẫn là giúp bạn xử lý các tác vụ bất đồng bộ một cách đồng thời, nhưng sẽ có thêm yếu tố chơi theo riêng lẻ hay theo nhóm.

Nếu bạn chưa biết về Structured Concurrency thì có thể tìm hiểu bài viết dưới đây trước. Để có được cái nhìn tổng quát và các bài toán mà chúng ta cần giải quyết.

    • Structured Concurrency & async let

Còn nếu mọi việc đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

Tất nhiên, với khái niệm mới này thì bạn cần chuẩn bị Swift của mình là mới nhất. Sau đây là version cho các tool của bạn.

    • Swift 5.5
    • macOS 12.0.x
    • Xcode 13.1

Đó là một tổ hợp các tool và môi trường phù hợp. Mình đã phải chờ rất lâu từ bản beta cho Swift 5.5 và Xcode 13 tới bản chính thức hiện tại này. Thì mới tiếp tục được Structured Concurrency & series New Concurrency trong Swift.

Về mặt kiến thức, bạn cần chuẩn bị kha khá kiến thức. Nhất là về bất đồng bộ. Bạn có thể tham khảo các bài viết về bất đồng bộ trên website này tại link dưới đây:

    • https://fxstudio.dev/tag/concurrency/

Về mặt demo, bạn có thể sử dụng Playground để thực thi các ví dụ. Nhưng nếu bạn sử dụng 1 iOS Project thì vẫn tốt hơn. Và chúng ta vẫn dùng console để hiển thị kết quả nhóe.

Task

Cập nhật từ Swift mới, với Structured Concurrency thì bạn sẽ được cung cấp thêm 2 kiểu mới. Đó là Task & TaskGroup. Giúp chúng ta chạy các tác vụ đồng thời theo cách riêng lẻ hoặc nhóm lại.

Ở dạng đơn giản nhất, bạn sẽ có Task. Tạo ra cho bạn một đối tượng để thực thi tác vụ mà bạn mong muốn. Ngay lập tức thì nó sẽ dược thưc thi ngày tại background. Bạn có thể sử dụng await để gán trị hoàn thành của nó trả về cho một biến nào đó.

Cú pháp

Bạn xem qua cú pháp cơ bản của nó nhóe. Ví dụ như sau:

let simpleTask = Task { () -> String in
    return "a simple task"
}

Bạn sẽ thấy cú pháp tạo ra một Task sẽ bao gồm các tham số sau:

Task(priority: <TaskPriority?>, operation: <() async -> _>)

  • Cung cấp độ ưu tiên thực thi cho tham số priority

  • operation là công việc cần được thi thực.

Mặc định, thì công việc của bạn sẽ là function với async nhóe. Có thể có giá trị trả về hoặc không. Với ví dụ trên, chúng ta sử dụng kiểu String trả về cho operation. Thực thi Task thì sẽ như thế này:

Task {
    print(await simpleTask.value)
}

Đơn giản là bạn tiếp tục tạo thêm 1 Task với kiểu Void để sử dụng kết quả của simpleTask. Hoặc bạn có thể sử dụng trong một function bất đồng bộ nào đó cũng được.

func doSomething() async {
    print("Begin")
    print(await simpleTask.value)
    print("End")
}

// thực thi
Task {
    await doSomething()
}

Quá EZ phải không nào!

Áp dụng

Tất nhiên, bạn sẽ áp dụng nó vào một bài toán cụ thể, thì mới thấy được công dụng của nó nhiều hơn. Chúng ta lấy ví dụ của việc in ra 50 số Fibonacy đầu tiên nhóe. Bạn sẽ có function tìm một số fibonacy thứ n như sau:

func fibonacci(of number: Int) -> Int {
    var first = 0
    var second = 1

    for _ in 0..<number {
        let previous = first
        first = second
        second = previous + first
    }

    return first
}

print(fibonacci(of: 15)) // số fibo thứ 15 là 610

Function sẽ lặp từ 0 tới number lần, để xác định được số fibo thứ number đó. Có nghĩa bạn đang có 1 for để làm việc ấy. Và bạn sẽ nâng cấp độ khó của bài toán n lần khi in ra dãy 50 số fibo đầu tiên nhóe. Có nghĩa lần này là for lồng for.

func printFibonacciSequence() {
    var numbers = [Int]()

    for i in 0..<50 {
        let result = fibonacci(of: i)
        numbers.append(result)
    }
    
    print("🔵 The first 50 numbers in the Fibonacci sequence are: \(numbers)")
}

printFibonacciSequence()

Điều này sẽ rất tốn tài nguyên của hệ thống. Vì tất cả công việc sẽ ném vào 1 thread và thưc thi lần lượt. Chúng ta sẽ giải quyết chúng bằng Task và cho chúng chạy đồng thời tại backgroud nhóe. Xem tiếp ví dụ nào!

func printFibonacciSequence2() async {
    let task1 = Task { () -> [Int] in
        var numbers = [Int]()

        for i in 0..<50 {
            let result = fibonacci(of: i)
            numbers.append(result)
        }

        return numbers
    }

    let result1 = await task1.value
    print("⚪️ The first 50 numbers in the Fibonacci sequence are: \(result1)")
}

Task {
    await printFibonacciSequence2()
}

Ví dụ trên các task tìm số fibo thứ n sẽ được chạy đồng thời với nhau. Tác vụ bắt đầu chạy ngay sau khi nó được tạo và hàm printFibonacciSequence() sẽ tiếp tục chạy trên bất kỳ chuỗi nào trong khi các số Fibonacci đang được tính toán.

Ví dụ cao cấp hơn nhóe:

let task1 = Task {
    (0..<50).map(fibonacci)
}

Task {
    print("🔴 The first 50 numbers in the Fibonacci sequence are: ")
    print(await task1.value)
}

Bạn chú ý các hình tròn màu mà mình để trong các lệnh print nhóe. Chúng sẽ thể thứ tự thực thi các task lớn.:

  • Màu xanh là đồng bộ. Các task khác sẽ chờ nó làm xong
  • Màu đỏ sẽ hoàn thành trước vì cấp độ nó ngang cấp với màu xanh
  • Màu xanh thì sẽ tạo ra các child task bên trong nó. Nó sẽ hoàn thành khi tất cả các child task hoàn thành.

Task priority

Khi tạo 1 task thì bạn sẽ được cung cấp các mức ưu tiên priority như sau:

  • high
  • default
  • low
  • background

Mặc định khi bạn không cấp cho tham số priority, thì nó sẽ nhận là default. Đối chiếu sang hệ thống thì bạn sẽ có quy ước như sau:

  • userInitiated = high
  • utility = low
  • Bạn không thể truy cập userInteractive, vì nó là main thread

Static methods

Chúng ta có các phương thức tĩnh của Task, giúp bạn điều kiển các Task thuận lợi hơn.

  • Task.sleep() task hiện tại sẽ sang chế độ ngủ. Đơn vị thời gian là nano giây. Nghĩa là 1_000_000_000 = 1 giây
  • Task.checkCancellation() kiểm tra xem ai đó có gỏi nó tự hũy hay không, bằng phương thức cancel(). Lúc ấy, sẽ ném về 1 giá trị CancellationError
  • Task.yield() dừng task hiện tại trong một thời gian. Để dành cho task khác đang chờ. Có ích trong các vòng for không lối thoát.

Ví dụ tổng hợp nhóe!

func cancelSleepingTask() async {
    let task = Task { () -> String in
        print("Starting")
        try await Task.sleep(nanoseconds: 1_000_000_000)
        try Task.checkCancellation()
        return "Done"
    }

    // The task has started, but we'll cancel it while it sleeps
    task.cancel()

    do {
        let result = try await task.value
        print("Result: \(result)")
    } catch {
        print("Task was cancelled.")
    }
}

Task {
    await cancelSleepingTask()
}

Trong đó:

  • Ngay khi task bắt đầu thì rơi vào lệnh ngủ 1 giây
  • Nhưng đã bị gọi cancel từ bên ngoài
  • Ở trong closure có kiểm tra việc có bị hũy hay không. Lúc này nó sẽ throw lỗi về tại do catch

Và khi lệnh hũy được đưa ra, thì giá trị value sẽ không được gởi về. Và để lấy được giá trị khi hũy vẫn diễn ra, thì bạn hay sử dụng tới task.result. Đó là 1 kiểu Result<String, Error> (theo như ví dụ trên). Công việc chỉ còn là switch … case mà thôi.

Task Group

Đối với nhiều công việc phức tạp, thì việc sử dụng các Task riêng lẻ, chúng sẽ không được hiệu quả cao nhất. Lúc này, bạn cần sử dụng tới Task Group. Nó sẽ tập hợp các nhiệm vụ (task) để thực hiện cùng nhau nhằm tạo ra 1 giá trị khi hoàn thành.

Task Group sẽ hạn chế nhỏ nhất rủi ro mà bạn sẽ gặp phải.

Cú pháp

Bạn không thể tạo ra Task Group một cách trực tiếp. Sử dụng function withTaskGroup() để tạo với nội dung công việc bạn muốn hoàn thành. Và bạn sẽ có 2 cách để tạo:

  • withThrowingTaskGroup
  • withTaskGroup

Chúng tương tự nhau chỉ khác nhau ở có throw và không mà thôi. Các tác vụ con sẽ được thêm vào Task Group thông qua phương thức addTask(). Các task con được thêm vào sẽ được thực thi ngay.

Áp dụng

Xem qua ví nhóe!

func printMessage() async {
    let string = await withTaskGroup(of: String.self) { group -> String in
        group.addTask { "Hello" }
        group.addTask { "From" }
        group.addTask { "A" }
        group.addTask { "Task" }
        group.addTask { "Group" }

        var collected = [String]()

        for await value in group {
            collected.append(value)
        }

        return collected.joined(separator: " ")
    }

    print(string)
}

// thực thi
Task {
    await printMessage()
}

Trong đó:

  • string là biến tạo ra để nhận giá trị cuối cùng task group sau khi hoàn thành.
  • Việc thêm các task con trong group thông qua phương thức group.addTask { }
  • Kiểu giá trị trả về của Task Group và Task con thường sẽ giống nhau.
  • Các giá trị của các task con mà bạn muốn lấy thì sẽ phải chờ await
  • Task group sẽ trả về giá trị sau khi tất cả các task con đã hoàn thành

Bạn sẽ hiểu mỗi Task con như là một function vậy. Tuy nhiên, chúng sẽ tự động thực thi & trong khi đó Task Group sẽ đợi tất cả hoàn thành trước khi trả về giá trị. Dẫn tới một điều rằng:

Đôi khi thứ tự trả về của các task con sẽ không như mong muốn.

With Error

Trường hợp, Task Group của bạn đang thực thi mà có lỗi phát sinh trong các task con, thì sẽ như thế nào? Bạn cần phải thiết kế lại việc tạo Task Group, lần này bạn sẽ dùng function withThrowingTaskGroup() để tạo Task Group. Đi kèm với đó là bạn sẽ cần sử dụng thêm try trước await. Vì có thể sinh ra lỗi trong quá trình thực thi.

Xem ví dụ nhóe!

enum LocationError: Error {
    case unknown
}

func getWeatherReadings(for location: String) async throws -> [Double] {
    switch location {
    case "London":
        return (1...100).map { _ in Double.random(in: 6...26) }
    case "Rome":
        return (1...100).map { _ in Double.random(in: 10...32) }
    case "San Francisco":
        return (1...100).map { _ in Double.random(in: 12...20) }
    default:
        throw LocationError.unknown
    }
}

func printAllWeatherReadings() async {
    do {
        print("Calculating average weather…")

        let result = try await withThrowingTaskGroup(of: [Double].self) { group -> String in
            group.addTask {
                try await getWeatherReadings(for: "London")
            }

            group.addTask {
                try await getWeatherReadings(for: "Rome")
            }

            group.addTask {
                try await getWeatherReadings(for: "San Francisco")
            }

            // Convert our array of arrays into a single array of doubles
            let allValues = try await group.reduce([], +)

            // Calculate the mean average of all our doubles
            let average = allValues.reduce(0, +) / Double(allValues.count)
            return "Overall average temperature is \(average)"
        }

        print("Done! \(result)")
    } catch {
        print("Error calculating data.")
    }
}

// thực thi
Task {
    await printAllWeatherReadings()
}

Trong ví du:

  • Các lệnh group.addTask hầu như giống nhau. Nên bạn có thể nhóm lại bằng 1 vòng lặp
  • Giá trị sẽ được làm gọn từ nhiều array double thành 1 array double, bằng toán tử reduce
  • Cuối cùng là tính giá trị trung bình của chúng

Tiếp theo, bạn đặt thử 1 thành phố không có trong dữ liệu vào Task Group và quan sát kết quả nhóe!

group.addTask {
    try await getWeatherReadings(for: "Hanoi")
}

Lúc này, Task Group sẽ gọi cancelAll() để hũy bất kỳ task còn nào trong nó. Nhưng các lệnh addTask vẫn sẽ được thực thi. Điều này gây ra sự tốn kém tài nguyên. Các khắc phục thì bạn sẽ sử dụng hàm thay thế addTaskUnlessCancelled(). Nó sẽ dừng việc thêm Task con khi Group phát lệnh hũy.

Tạm kết

Ở trên, mình chỉ giới thiệu lại cơ bản của Task & Task Group trong Swift 5.5 mà thôi. Còn việc áp dụng của nó tùy thuộc vào bạn nắm được bao nhiêu kiến thức của New Concurrency trong Swift mới. Còn các khái niệm Concurrency mới có sự liên hệ chặt chẽ với nhau. Và hỗ trợ nhau rất nhiều. Mình sẽ trình bày ở các bài viết khác.

 

Okay! Tới đây, mình xin kết thúc bài viết giới thiệu về Task & Task Group trong Swift 5.5 . Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

  • Bài viết tiếp theo tại đây.
  • Bạn có thể checkout code tại đây.

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares25

Related Posts:

  • Convenience Initializer trong 10 phút
    Convenience Initializer trong 10 phút
  • File Manager trong 10 phút - Swift
    File Manager trong 10 phút - Swift
  • Cơ bản về async/await trong 10 phút - Swift 5.5
    Cơ bản về async/await trong 10 phút - Swift 5.5
  • Generics trong 10 phút - Swift
    Generics trong 10 phút - Swift
Tags: concurrency, Swift, Swift 5.5
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • Cơ bản về async/await trong 10 phút - Swift 5.5
    Cơ bản về async/await trong 10 phút - Swift 5.5
  • UserDefaults trong 10 phút
    UserDefaults trong 10 phút
  • Keychain trong 10 phút - iOS
    Keychain trong 10 phút - iOS
  • Generics trong 10 phút - Swift
    Generics trong 10 phút - Swift
  • Guard Keyword trong 10 phút
    Guard Keyword trong 10 phút

Archives

  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!