Skip to content

Instantly share code, notes, and snippets.

@robertmryan
Last active January 11, 2026 19:35
Show Gist options
  • Select an option

  • Save robertmryan/a186546becfdca50ce45eaf3098e441d to your computer and use it in GitHub Desktop.

Select an option

Save robertmryan/a186546becfdca50ce45eaf3098e441d to your computer and use it in GitHub Desktop.
// The three child functions can presumably fail for a variety of reasons:
//
// 1. The request of data from the cloud failed.
// 2. The saving of the data locally failed.
// 3. The function was canceled.
//
// And for each of these, you want to let the parent task know why it failed.
// So, we would generally mark these child functions as `throws`.
//
// And we would explicitly avoid ever using `try?` or `try!` in these child
// functions. And we’d avoid a `catch` block (to allow errors to percolate up
// to the caller that will update the UI), or if the child function did need
// a `catch` block, we’d also have that block re-`throw` the error.
//
// We would also avoid introducing any unnecessary unstructured
// concurrency (e.g., `Task {…}` or `Task.detached {…}`) in these child
// functions. As an aside, in the unlikely case that unstructured concurrency,
// was needed, we would wrap that in a `withTaskCancellationHandler`, but that
// is beyond the scope of the question.
func updateDrumsFromTheCloud() async throws {…}
func updateMixFromTheCloud() async throws {…}
func updateClapsFromTheCloud() async throws {…}
// Anyway, we can then just `try await` these calls, with no explicit
// `Task.isCancelled` checks:
func updateEverythingFromTheCloud() async throws {
try await updateDrumsFromTheCloud() // these take a minute
try await updateMixFromTheCloud()
try await updateClapsFromTheCloud()
}
// And then the caller might, for example, create the `Task` for this async
// work, and then update the UI accordingly:
var updateTask: Task<Void, Never>?
updateTask = Task {
do {
try await updateEverythingFromTheCloud()
await refreshUI()
} catch {
await updateUI(for: error)
}
}
@robertmryan
Copy link
Author

robertmryan commented Jan 10, 2026

In the above, note that cancelling updateTask will propagate that to the three child functions automatically because we have remained within the realm of structured concurrency. But also note that no explicit checks of Task.isCancelled (or better, try Task.checkCancellation()) are needed.

As an aside, on the basis of these function names, it does not appear that the results of one call are dependent upon the other. So we might let these run concurrently:

func updateEverythingFromTheCloud() async throws {
    try await withThrowingDiscardingTaskGroup { group in
        group.addTask { try await updateDrumsFromTheCloud() }
        group.addTask { try await updateMixFromTheCloud() }
        group.addTask { try await updateClapsFromTheCloud() }
    }
}

Like the original example above, cancelling updateTask for this implementation will also automatically propagate cancellation to these three child functions.

@robertmryan
Copy link
Author

robertmryan commented Jan 10, 2026

By the way, if you want advice on how to make these three child functions handle cancellation properly, maybe you can share an example implementation of be of them, and we can advise further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment