URLSession and Swift Concurrency: Does cancelling a Task abort the underlying dataTask?
0 reputation · 18 Dec 2021, 10:07 UTC
0 reputation · 18 Dec 2021, 10:07 UTC
When a Swift Task is cancelled, ensure that the associated URLSessionDataTask is terminated immediately to prevent unnecessary network usage and battery drain.
URLSession’s data(for:) overload forwards cancellation only when explicitly used; otherwise cancelling the enclosing Task does not affect the underlying network task. Similarly, Combine’s .timeout operator emits a timeout error but does not cancel the upstream URLSessionDataTask. The documented behavior of Swift 6 structured concurrency regarding automatic propagation of cancellation to URLSession is not clearly specified, leaving developers to guess whether additional manual cancellation is required.
URLSession.shared.data(for:) automatically cancel the underlying URLSessionDataTask when using the overload that forwards cancellation?.timeout when applied to a URLSession dataTaskPublisher with respect to the upstream network task?29275 reputation · 18 Dec 2021, 13:05 UTC
URLSession.shared.data(for:) automatically cancels the underlying network request.In modern Swift concurrency, the data(for:delegate:) methods in URLSession are designed to be cancellation-aware. When the Task awaiting these methods is cancelled, the cancellation signal propagates to the underlying URLSessionDataTask, triggering an immediate abort of the network activity to save system resources.
The behavior depends entirely on which API you are using. Here is the breakdown:
data(for:)): Cancellation is automatically forwarded. If you cancel the Task, the request is cancelled, and the method will throw a URLError.cancelled (code -999).dataTask(with:)): If you are manually wrapping the old dataTask in a CheckedContinuation, cancelling the Task will not stop the request. You must explicitly use withTaskCancellationHandler to manually call task.cancel()..timeout): The timeout operator emits an error to the subscriber but does not automatically cancel the upstream dataTaskPublisher unless the subscription itself is cancelled via AnyCancellable.To ensure your network requests are aborted properly when a user navigates away or cancels an operation:
let task = Task {
do {
let (data, response) = try await URLSession.shared.data(for: request)
// Process data
} catch is CancellationError {
// Handle cancellation specifically
print("Request was aborted")
} catch {
// Handle other errors
}
}
// Later, to abort the request:
task.cancel()
| Method | Auto-Abort? | Error Type |
|---|---|---|
data(for:) |
Yes | URLError.cancelled |
dataTask(with:) |
No | Manual task.cancel() required
|
| Combine .timeout | No | Upstream remains active |
Diagnostic Question: Are you currently using the native data(for:) async method, or are you wrapping a legacy completion-handler task in a continuation? This determines if manual cleanup is required.
Use comments to ask for clarification. Post a solution as an answer.
29,275 reputation · 18 Dec 2021, 11:43 UTC
The Swift concurrency overload URLSession.data(for:) (available on iOS 15/macOS 12+) forwards a cancellation signal to the underlying URLSessionDataTask only while the Task is actually awaiting that call. If you detach the work or store the returned Task without awaiting, cancelling the outer Task will not affect the network request.
let task = Task {
await withCheckedContinuation { continuation in
let dataTask = URLSession.shared.dataTask(with: request) { data, resp, err in
continuation.resume(with: Result { try err.map { throw $0 }; return (data!, resp!) })
}
// Link Swift cancellation to the dataTask
continuation.resume(onCancellation: { dataTask.cancel() })
dataTask.resume()
}
}
// Later: task.cancel() will now cancel the dataTask
Thus, the pattern to guarantee abort is either use the modern async overload or manually tie cancellation via withTaskCancellationHandler (or CheckedContinuation.resume(onCancellation:)).