79759757

Date: 2025-09-09 10:18:04
Score: 0.5
Natty:
Report link

I have encountered and solved exactly the same scenario as your problem

I used to work at a self-driving company in charge of HD map creation. The tile image loading pattern is exactly the same as yours. I recently abstracted the solution for this type of problem into a framework Monstra , which includes not only task scheduling but also on-demand task result caching.

Here are the details:


The Problem: Swift Concurrency Task Scheduling

Your issue is a classic concurrency problem where Swift's task scheduler prioritizes task fairness over task completion. When you create multiple Tasks, the runtime interleaves their execution rather than completing them sequentially.

Two solutions

Option 1: KVLightTasksManager

For simpler task management with batch execution capabilities:

import Monstra

actor Processor {
    private let taskManager: KVLightTasksManager<Int, ProcessingResult>
    
    init() {
        // Using MonoProvider mode for simplicity
        self.taskManager = KVLightTasksManager(
            config: .init(
                dataProvider: .asyncMonoprovide { value in
                    return try await self.performProcessing(of: value)
                },
                maxNumberOfRunningTasks: 3, // Match your CPU cores
                maxNumberOfQueueingTasks: 1000
            )
        )
    }
    
    func enqueue(value: Int) {
        taskManager.fetch(key: value) { key, result in
            switch result {
            case .success(let processingResult):
                print("Finished processing", key)
                self.postResult(processingResult)
            case .failure(let error):
                print("Processing failed for \(key):", error)
            }
        }
    }
    
    private func performProcessing(of value: Int) async throws -> ProcessingResult {
        // Your CPU-intensive processing
        async let resultA = performSubProcessing(of: value)
        async let resultB = performSubProcessing(of: value)
        async let resultC = performSubProcessing(of: value)
        
        let results = await (resultA, resultB, resultC)
        return ProcessingResult(a: results.0, b: results.1, c: results.2)
    }
    
    private func performSubProcessing(of number: Int) async -> Int {
        await Task.sleep(nanoseconds: 1_000_000_000) // 1 second
        return number * 2
    }
}

struct ProcessingResult {
    let a: Int
    let b: Int 
    let c: Int
}

Key advantages:

Solution 2: KVHeavyTasksManager

For your specific use case with controlled concurrency, use KVHeavyTasksManager:

import Monstra

actor Processor {
    private let taskManager: KVHeavyTasksManager<Int, ProcessingResult, Void, ProcessingProvider>
    
    init() {
        self.taskManager = KVHeavyTasksManager(
            config: .init(
                maxNumberOfRunningTasks: 3, // Match your CPU cores
                maxNumberOfQueueingTasks: 1000, // Handle your 1000 requests
                taskResultExpireTime: 300.0
            )
        )
    }
    
    func enqueue(value: Int) {
        taskManager.fetch(
            key: value,
            customEventObserver: nil,
            result: { [weak self] result in
                switch result {
                case .success(let processingResult):
                    print("Finished processing", value)
                    self?.postResult(processingResult)
                case .failure(let error):
                    print("Processing failed:", error)
                }
            }
        )
    }
}

// Custom data provider
class ProcessingProvider: KVHeavyTaskDataProviderInterface {
    typealias Key = Int
    typealias FinalResult = ProcessingResult
    typealias CustomEvent = Void
    
    func asyncProvide(key: Int, customEventObserver: ((Void) -> Void)?) async throws -> ProcessingResult {
        // Your CPU-intensive processing
        async let resultA = performSubProcessing(of: key)
        async let resultB = performSubProcessing(of: key)
        async let resultC = performSubProcessing(of: key)
        
        let results = await (resultA, resultB, resultC)
        return ProcessingResult(a: results.0, b: results.1, c: results.2)
    }
    
    private func performSubProcessing(of number: Int) async -> Int {
        // Simulate CPU work without blocking the thread
        await Task.sleep(nanoseconds: 1_000_000_000) // 1 second
        return number * 2
    }
}

Why This Solves Your Problem

  1. Controlled Concurrency: KVHeavyTasksManager limits concurrent tasks to match your CPU cores
  2. Queue Management: Automatically queues excess requests instead of creating unlimited tasks
  3. Task Completion: Ensures tasks complete fully before starting new ones
  4. Resource Management: Prevents memory issues from thousands of simultaneous tasks
  5. Result Caching: Avoids duplicate processing for the same values

Performance Benefits

Installation

Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/yangchenlarkin/Monstra.git", from: "0.1.0")
]

CocoaPods:

pod 'Monstra', '~> 0.1.0'

Alternative Without Monstra

If you prefer a pure Swift solution, you need to implement proper task coordination:

actor Processor {
    private var currentProcessingCount = 0
    private let maxConcurrent = 3
    private var waitingTasks: [Int] = []
    
    func enqueue(value: Int) async {
        if currentProcessingCount < maxConcurrent {
            await startProcessing(value: value)
        } else {
            waitingTasks.append(value)
        }
    }
    
    private func startProcessing(value: Int) async {
        currentProcessingCount += 1
        
        await performProcessing(of: value)
        
        currentProcessingCount -= 1
        
        // Start next waiting task
        if !waitingTasks.isEmpty {
            let nextValue = waitingTasks.removeFirst()
            await startProcessing(value: nextValue)
        }
    }
}

However, this requires significant error handling, edge case management, and testing - which Monstra handles for you.


Full disclosure: I'm the author of Monstra. Built it specifically to solve these kinds of concurrency and task management problems in iOS development. The framework includes comprehensive examples for similar use cases in the Examples folder.

Reasons:
  • Contains signature (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Larkin