Swift Concurrency (Part 03) — Task cancellation and call tasks in a loop.

Habibur Rahman Ovie
3 min readDec 5, 2023

--

In this article, we will perform a series of tasks. To begin, we will initiate a loop to execute these tasks. For ease of understanding, we’ll treat a delay function as an asynchronous function that consumes time. This function has been previously discussed in our articles, and its details won’t be reiterated here for brevity.

private func delayAfter(delay: Int) async throws -> Int {

try await Task.sleep(
until: .now + .seconds(delay),
clock: .suspending
)

return delay
}

We will invoke the delayAfter function iteratively within a loop.

var delays = [1,2,3,4]

func performTaskLoops() async throws {
for delay in delays {
let d = try await delayAfter(delay: delay)
print(d)
}
}

In this context, delays is an array of integers. Within our performTaskLoops function, we iterate through the delays array and invoke the delayAfter function inside the loop. It’s important to note that as we wait in each iteration, the function won’t be called asynchronously within the loop. To enhance clarity, let’s execute the function.

Task{

let startTime = DispatchTime.now()

try await performTaskLoops()

let endTime = DispatchTime.now()
let elapsedTime = Double(endTime.uptimeNanoseconds - startTime.uptimeNanoseconds) / 1_000_000_000
print("ElapsedTime \(elapsedTime)")
}

In this instance, we have incorporated a timer to assess the execution time. Upon running the code, the output will be as follows:

1
2
3
4
ElapsedTime 10.669170042

The code is designed to print “1” after 1 second, followed by “2” after 2 seconds from the first output. Subsequent values (3 and 4) will be printed after 3 and 4 seconds from their respective previous outputs. The total execution time sums up to approximately 10 seconds. Essentially, the loop executes synchronously. To achieve asynchronous execution, we will explore the use of an async group in the next article.

Now let's refactor the delayAfter function and throw an error if the delay is even.


enum CustomError: Error {
case delayError
}

private func delayAfter(delay: Int) async throws -> Int {

if delay % 2 == 0 {
throw CustomError.delayError
}
try await Task.sleep(
until: .now + .seconds(delay),
clock: .suspending
)

return delay
}

Now if we call the performTaskLoops function then it will only print 1 and do not execute further. Because when the delay is 2 then delayAfter throws an error and doesn’t return value.


func performTaskLoopsInCancelable() async throws {

var cancelledTaskDelays = [Int]()

for delay in delays {
do {
try Task.checkCancellation()
let d = try await delayAfter(delay: delay)
print(d)
}catch{
cancelledTaskDelays.append(delay)
}
}

print("Cancelled delays \(cancelledTaskDelays)")
}

In this context, we employ Task.checkCancellation() to verify whether the task has been canceled. If it is canceled, we can readily retrieve information about the canceled task within the catch block.

1
3
Cancelled delays [2, 4]
ElapsedTime 4.266897625

The output of this function is self-explanatory.

Check the first and second parts.
Get the full code from GitHub.
Connect me on LinkedIn.

--

--

Habibur Rahman Ovie
Habibur Rahman Ovie

No responses yet