[Concurrency] Continuations for interfacing async tasks with synchronous code

I think that querying cancellation & local data make sense semantically from the UnsafeContinuation object, at least up until the resume is called. We could treat everything until resume to be part of the task since the task is suspended until then. Task local data would be stable, and cancel are synchronized anyway. After resume, these could just trap.

While I don't see much problem in omitting task local (we can use capture list as others pointed out), we probably need cancellation, since that's the only ever-changing thing that's queried inside a task.

Based on the current structured concurrency pitch, I would expect this to be written as follows, no continuation-specific cancellation functionality needed:

func download(url: URL) async throws -> Data? {
    var urlSessionTask: URLSessionTask?

    return try Task.withCancellationHandler { urlSessionTask?.cancel() }
    operation: {
        return try await withUnsafeThrowingContinuation { continuation in
            urlSessionTask = URLSession.shared.dataTask(with: url) { data, _, error in
                if let error = error {
                    // Ideally translate NSURLErrorCancelled to CancellationError here
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: data)
                }
            }
            urlSessionTask?.resume()
        }
    }
}
1 Like

Nothing is being ignored, it has nothing to do with this specific API, so there's no reason to cram in more complexity into it.

Through composition with APIs proposed in structured concurrency, it is able to handle cancelation when necessary, exactly like @jayton suggests (that's one of the ways):

Note that since you're in an async function already (in download), you can normally Task.isCanceled check before submitting any work as well.

Please refer to [Pitch #2] Structured Concurrency - #116 by ktoso for more details on structured concurrency and those APIs; If we made Task a value, then the insides of the continuation can check for it: task.isCanceled as well.

3 Likes

Thanks for this clear answer. Questions are not there to create difficulties, but to get answers to the expected use cases that are not covered in the proposal text. :+1:

2 Likes

No worries, thanks for the feedback -- we're adding addressing those questions to the proposal here: Introduce a dedicated proposal for `with*Continuation` by jckarter Β· Pull Request #1244 Β· apple/swift-evolution Β· GitHub :+1:

1 Like

I've updated the proposal to propose trapping on multiple resumes, and added some examples and discussion from this thread. Thanks everyone.

8 Likes

How about switching checked or unchecked automatically depending on if -Ounchecked (or maybe -O) flag is on? It seems consistent with precondition (or assert).

1 Like

I don't think we can do that automatically because the types are not layout-compatible; CheckedContinuation needs to hold on to a class instance whose lifetime tracks whether the continuation is leaked, whereas UnsafeContinuation can be implemented as a raw pointer to the task structure.

2 Likes

I got it. Thank you.

Please note that this proposal has now entered the review phase: SE-0300: Continuations for interfacing async tasks with synchronous code :eyes:

2 Likes