'Why does this swift async-await code fails with leaked continuation?

I am experimenting with Swift async-await and AsyncSequence protocol,

Here is the code:

struct AsyncNumbers<Element: Numeric>: AsyncSequence {
    private let numbers: [Element]

    init(_ numbers: [Element]) {
        self.numbers = numbers
    }

    func makeAsyncIterator() -> AsyncNumberIterator {
        return AsyncNumberIterator(numbers)
    }
}

extension AsyncNumbers {
    struct AsyncNumberIterator: AsyncIteratorProtocol {
        private let numbers: [Element]
        private var index = -1

        init(_ numbers: [Element]) {
            self.numbers = numbers
        }

        mutating func next() async -> AsyncNumbers<Element>.Element? {
            index += 1
            return await withCheckedContinuation { [self] continuation in
                Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { timer in
                    guard index < numbers.count else {
                        continuation.resume(returning: nil)
                        timer.invalidate()
                        return
                    }
                    continuation.resume(returning: numbers[index])
                }
            }
        }
    }
}

func printNumbers() async {
    let numbers = AsyncNumbers([5,78,3,45,99,100,23,4,7,8,9])
    for await num in numbers {
        print("Number:", num)
    }
    print("End")
}

Task {
    await printNumbers()
}

This code fails before even the first number is printed to the console, with error SWIFT TASK CONTINUATION MISUSE: next() leaked its continuation!,

I don't understand why I am getting this error here, the timer block is called only once after 2 seconds no duplicate calls to the continuation block are happening here,

Does anyone know what might the issue?

Thank you in advance :)



Solution 1:[1]

As several rightly commented, the problem is your use of Timer and Concurrency.

Timer.scheduledTimer requires a runloop to work. In contrast to the main thread, secondary threads do not have a runloop until you explicitly create it.

Since you are calling this from a coroutine in the body of an await function, your thread context is a secondary thread. This means that at the time of you calling Timer.scheduledTimer you are not having a runloop, hence the timer callback will never be called and – depending on the context of the surrounding code – Swift rightly warns you that you are leaking the coroutine.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 DrMickeyLauer