How to cancel a Task in Swift


Greetings, traveler!

When working with Concurrency in Swift, we can cancel a task by calling its cancel() method. However, it is important to note that this cancellation is cooperative, which implies that the task must periodically check if it has been canceled via Task.isCancelled and take appropriate actions. Moreover, you can call the Task.checkCancellation() method to check the task state. If it was canceled, you will get a CancellationError. If the task is part of a group and any member of this group throws an error, the other tasks will be canceled.

Example

Let’s look at an example using the URLSession. But before we do that, I have to say that the URLSession has a built-in mechanism for checking the cancellation of a task. If the task is canceled, we will get a URLError. Therefore, if we write such code, it will not compile.

func loadImage(url: URL) async -> UIImage {
    let request = URLRequest(url: url)
    
    let (data, _) = try await URLSession.shared.data(for: request) // ❌ Compile error: Errors thrown from here are not handled
    
    guard let image = UIImage(data: data) else {
        return
    }
    
    return image
}

So, first, we will fix this code using async throws.

enum ImageError: Error {
    case invalidData
}

func loadImage(url: URL) async throws -> UIImage {
    let request = URLRequest(url: url)
    
    let (data, _) = try await URLSession.shared.data(for: request) // ✅ Compiled successfully
    
    guard let image = UIImage(data: data) else {
        throw ImageError.invalidData
    }
    
    return image
}

Since the automatic verification occurs before the network request, we can create the same one afterward. We will do it with the Task.checkCancellation() method. This static method checks the task within which it was called for cancellation.

enum ImageError: Error {
    case invalidData
}

func loadImage(url: URL) async throws -> UIImage {
    let request = URLRequest(url: url)
    
    let (data, _) = try await URLSession.shared.data(for: request)
    
    try Task.checkCancellation()
    
    guard let image = UIImage(data: data) else {
        throw ImageError.invalidData
    }
    
    return image
}

Alternatively, you can handle the cancellation check using the static property Task.isCancelled.

enum ImageError: Error {
    case invalidData
    case canceled
}

func loadImage(url: URL) async throws -> UIImage {
    let request = URLRequest(url: url)
    
    let (data, _) = try await URLSession.shared.data(for: request)
    
    if Task.isCancelled {
        throw ImageError.canceled
    }
    
    guard let image = UIImage(data: data) else {
        throw ImageError.invalidData
    }
    
    return image
}

Conclusion

We have considered a useful task cancellation mechanism that will save system resources by not performing calculations for those tasks whose relevance has already been lost.

In the following article, we will determine the difference between a Task and a detached Task. See you there!