# Is Task Cancellation totally cooperative?

**URL:** <https://forums.swift.org/t/is-task-cancellation-totally-cooperative/83100>\
**Category:** Using Swift\
**Tags:** concurrency\
**Created:** [November 8, 2025, 10:13pm UTC](https://forums.swift.org/t/is-task-cancellation-totally-cooperative/83100 "2025-11-08T22:13:48Z")\
**Posts on this page:** 1\
**Showing post:** 2

<div class="post-metadata">

**Author:** ![robert.ryan](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/robert.ryan/32/38339_2.png) [@robert.ryan](https://forums.swift.org/u/robert.ryan)\
**Post date:** [November 8, 2025, 11:18pm UTC](https://forums.swift.org/t/is-task-cancellation-totally-cooperative/83100/2 "2025-11-08T23:18:48Z")

</div>

> [@EndBird](#):
>
> So if I have no code in any of the async methods invoked in a Task’s body nor the Task itself to check if the Task has been cancelled, would the call to cancel the Task do nothing?

Correct.

Fortunately, most `async` API that we call and `await` will detect cancellation for us. And if calling other asynchronous functions, remaining within structured concurrency will greatly simplify the effort to make sure cancellations are propagated to child tasks automatically.

This only becomes a pragmatic concern if you either:

- call legacy synchronous API;
- have a long-running task that neglects to periodically call `try Task.checkCancellation()`; or
- introduce unstructured concurrency and accidentally neglect to add a cancellation handler.

As I mentioned in my [answer](https://forums.swift.org/t/async-let-created-tasks/83095/4) to your other question, this is why we are also very wary of introducing unnecessary unstructured concurrency, and where we bear the burden for manually detecting/propagating cancellation with [`withTaskCancellationHandler`](https://developer.apple.com/documentation/swift/withtaskcancellationhandler(operation:oncancel:isolation:)/).

> [@EndBird](#):
>
> And how does this translate to structured concurrency for TaskGroups and `async let` calls? Would a call to cancel the Task running just mark the child tasks as cancelled but it’s their responsibility to actually handle anything rather than the cancellation terminating any running code in the children?

Yes, all cancellation in Swift concurrency is “cooperative”, not “preemptive”. This means that our task must either detect and handle cancellation itself, or, more commonly, merely `await` something that does this for us, which most `async` API do. So, if you remain with structured concurrency and are calling `async` API, all of this is generally a non-issue, as the API will detect cancellation and throw `CancellationError` if cancelled and we’re done. It largely only becomes problematic in the case of some legacy API or writing your own long-running synchronous code. (The latter can frequently be addressed by periodically calling `try Task.checkCancellation()`.)

> [@EndBird](#):
>
> And if a Task hasn’t started yet, would it still run after the parent task has been cancelled? Or it wouldn’t even.

When we use `Task {…}`, that will not create a child task, but rather, “a new unstructured top-level task.” Cancelling the “parent” task will therefore have no effect on these separate top-level tasks it created (unless you manually do so). So, yes, these other top-level tasks created by the “parent” will still run.

But even within structured concurrency, where the cancellation _is_ propagated for us, the child task still must detect and handle cancellation. Generally, if it’s just awaiting some `async` API, we get this behavior for free. But we can contrive an example where cancellation is not detected/handled:

```swift
let task = Task {
    let foo = Foo()

    async let task1: () = foo.bar()
    async let task2: () = foo.bar()
    async let task3: () = foo.bar()
    _ = await (task1, task2, task3)
}

Task {
    try await Task.sleep(for: .seconds(0.5))
    task.cancel()
}

actor Foo {
    func bar(_ duration: Duration = .seconds(1)) {
        let start = ContinuousClock.now
        while start.duration(to: .now) < duration { 
            // intentionally spinning; simulating some slow, synchronous work that doesn’t detect cancellation
        }
    }
}

```

In this case, because `Foo.bar` is an actor isolated method, despite using `async let`, `task2` will not run until `task1` finishes. And although the main `Task {…}` is cancelled while `task1` is still running, none of these calls to `Foo.bar` will detect cancellation, and therefore the top level task will blithely carry on despite having been cancelled.

However, if I call some API that detects and handles cancellation (like calling `try await Task.sleep(for:)` instead of spinning, or periodically calling `try Task.checkCancellation`), then these tasks will be cancelled:

```swift
actor Foo {
    func bar(_ duration: Duration = .seconds(1)) throws {
        let start = ContinuousClock.now
        while start.duration(to: .now) < duration {
            try Task.checkCancellation()
        }
    }
}

```

Now I’ve illustrated the `try Task.checkCancellation` pattern to simulate a slow, synchronous calculation, but that’s obviously a bit of an edge case scenario. The vast majority of our Swift concurrency code is just performing an `await` of some `async` API (or our own `async` methods that are awaiting other functions). And because most `async` API we call almost always handle cancellation for us, all of this manual checking for cancellation is a non-issue.

The big gotcha for new developers is where they start sprinkling their code with unstructured concurrency (i.e., `Task {…}`) without appreciating the burden that places on their shoulders, namely the manual propagation of cancellation. If you remain within structured concurrency (and favor modern `async` API over legacy API), life is much easier.

* * *

I should note that all of this cancellation conversation is really independent of the `async let` and task group factors. It really applies to all Swift concurrency programming. Wherever possible, write code that handles cancellation properly (which you effectively get for free when calling most `async` API). Just avoid/minimize the use of the three anti-patterns I enumerated above.

---

_[View the full topic](https://forums.swift.org/t/is-task-cancellation-totally-cooperative/83100)._
