Tasks

Tasks

Intro

Could be described as a single unit of work needed to be completed could be described as a task.
Don't confuse this with process, threads, CPU Core.

Those are more of combination of multiple tasks -> Thread

Multiple threads + resources -> Process

Multiple threads could run on single core.

Multiple processes could run on single core.

Code

Swift Concurrency Task example
Task allows us to create concurrent tasks and call that from a non concurrent method using async/await.

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

Lots of more examples of Task part of concurrency framework. WWDC 2021.
avanderlee | concurrency Tasks

Context Switching

CPU context switching happens using different time sharing / slicing algorithms and OS handles the priority of things to queue up to make sure efficient usage of limited resources at its disposal.

Now the strategy for handling resources would differ by lot of factors.
Architecture CPU - arm, arm64, intelx86, amd64, RISC-V.
I/O - DDR2, 3, 4, LPDDR, SSD, Nand, eMMC, UFS, M.2 NVME, Hard Drives SATA 2, 3.
CPU Cache - L1, L2, L3
ECC Memory
Hardware accelerators
Security Modules - TPM, Apple T1, T2 chips, Enterprise Encryption
OS security - Encryption, Hardware encryption.

My point is Architecture isn't easy and there are lots of trade offs for every decision we make.

One could prefer Windows, macOS (unix subsystem), Debian flavored Linux, LinuxSE, Ubuntu.
UI - KDE plasma, Gnome, Windows Start, MacOS Springboard / launchpad / dock.

Btw I use Arch Linux. r/linuxMasterRace meme

Delay in Task

Adding a timer in Swift language doc

Async context

func foo() async {
    try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))
    // Put your code which should be executed with a delay here
}

how to create a delay in swift SO


let loadingSpinnerTask = Task {
	try await Task.sleep(nanoseconds: 150_000_000)
	showLoadingSpinner()
}

Task {
	await prepareVideo()
	loadingSpinnerTask.cancel()
	hideLoadingSpinner()
}

delaying-an-async-swift-task | swift by sundell

GCD

DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
    // Put your code which should be executed with a delay here
}

Delay A Function Call and Sleep A Thread in Swift

Articles

SwiftUI Tasks vs OnAppear

Reference

It would be good to maintain a reference to the task so it could be cancelled at any time.

  1. Ensures there aren't multiple tasks happening at the same time
  2. gives you something to kill on dealloc

Result Type

Code snippet from HWS

enum LoadError: Error {
    case fetchFailed, decodeFailed
}

func fetchQuotes() async {
    let downloadTask = Task { () -> String in
        let url = URL(string: "https://hws.dev/quotes.txt")!
        let data: Data

        do {
            (data, _) = try await URLSession.shared.data(from: url)
        } catch {
            throw LoadError.fetchFailed
        }

        if let string = String(data: data, encoding: .utf8) {
            return string
        } else {
            throw LoadError.decodeFailed
        }
    }

    let result = await downloadTask.result

    do {
        let string = try result.get()
        print(string)
    } catch LoadError.fetchFailed {
        print("Unable to fetch the quotes.")
    } catch LoadError.decodeFailed {
        print("Unable to convert quotes to text.")
    } catch {
        print("Unknown error.")
    }
}

await fetchQuotes()

Do try catch Errors

func fetchUpdates() async {
    let newsTask = Task { () -> Data in
        let url = URL(string: "https://hws.dev/headlines.json")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }

    let highScoreTask = Task { () -> Data in
        let url = URL(string: "https://hws.dev/scores.json")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }

    do {
        let news = try await newsTask.value
        let highScores = try await highScoreTask.value
    } catch {
        print("There was an error loading user data.")
    }
}

await fetchUpdates()

Hacking with Swift | Create a task

SO | throwing errors in tasks

Yield | Suspend

await Task.yield()

HWS | voluntarily suspend a task

Mind Map

ARC in Tasks | Reference Counting

Swift UI Tasks

SwiftUI provides a task() modifier that starts a new task as soon as a view appears, and automatically cancels the task when the view disappears. This is sort of the equivalent of starting a task in onAppear() then cancelling it onDisappear(), although task() has an extra ability to track an identifier and restart its task when the identifier changes.

HWS | runs tasks with SwiftUI lifecycle

// Equatable 
struct Message: Decodable, Identifiable {
    let id: Int
    let user: String 
}
@State private var messages = [Message](/404)

NavigationStack {
	// DO UI Stuff
}
.task {
	await fetchData()
}


 func fetchData() async {
	do {
		let url = URL(string: "https://hws.dev/inbox.json")!
		let (data, _) = try await URLSession.shared.data(from: url)
		messages = try JSONDecoder().decode([Message].self, from: data)
	} catch { }
	

// Async Tasks
struct NumberGenerator: AsyncSequence, AsyncIteratorProtocol {

mutating func next() async -> Int? { 
	while Task.isCancelled == false {
		return Int.random(in: 1...500)
	}
	return nil
}

func makeAsyncIterator() -> NumberGenerator { self }

}

@State private var numbers = [String](/404)
let generator = NumberGenerator()

NavigationStack { }
.task {
	await generateNumbers()
}

func generateNumbers() async {
	for await number in generator {
		numbers.insert("\(numbers.count + 1). \(number)", at: 0)
	}
}

Task as property

Various ways to get the task or subscript task or await async state in a function with retrieve reusability in mind.

final class AdBeaconWrapper {
    private(set) var adBreakContainer: Set<AdBreakContainer> = []
    fileprivate var adMapProvider: AdMapProvider
   
    func retrieveCurrentAdBreak(_ timelineSegment: TimelineSegment) -> EntosAd.AdBreak? {
        guard let currentAdContainer = retrieveCurrentAdContainer(timelineSegment) else {
            log("Cant find Ad container for associated unique ID", level: .warning)
            return nil
        }

        return currentAdContainer.entOsAdBreak
    }
}

final class AdBeaconWrapper {
    subscript(adBreak segment: TimelineSegment) -> EntosAd.AdBreak {
        get async throws {
            let retrieveAdBreak: Task<EntosAd.AdBreak, Swift.Error> = Task {
                guard let adBreak = retrieveCurrentAdBreak(segment) else {
                    throw AdBeaconError.couldntEmitAdBreak
                }
                return adBreak
            }

            return try await retrieveAdBreak.value
        }
    }


    func prepare(segment: TimelineSegment) {
    	Task {
            let adBreak = try await self[adBreak: segment]
        }
    }
}


```swift
final class AdBeaconWrapper {
    func prepare(segment: TimelineSegment) {
    	let retrieveAdBreak: Task<EntosAd.AdBreak, Swift.Error> = Task {
            guard let adBreak = retrieveCurrentAdBreak(segment) else {
                throw AdBeaconError.couldntEmitAdBreak
            }
            return adBreak
        }

    	Task {
            let adBreak = try? await retrieveAdBreak.value
        }
    }
}
final class AdBeaconWrapper {
	lazy var retrieveSetOut: Task<Set<AdBreakContainer>, Never> = Task {
        adBreakContainer = await adMapProvider.getAdBreakContainer()
        return adBreakContainer
    }

    func prepare(segment: TimelineSegment) {
    	let retrieveSet: Task<Set<AdBreakContainer>, Never> = Task {
            adBreakContainer = await adMapProvider.getAdBreakContainer()
            return adBreakContainer
        }

    	Task {
            let adBreak = try? await retrieveSet.value

            let adbreakContainer = await retrieveSetOut.value
        }
    }
}
```swift