Async, Rethrows, and Defer

Re-skimming the thread myself, it's mostly about async let, so probably not super relevant. But at least it's a sign that async defer blocks are being considered.

1 Like

@Joe_Groff Having read through the linked thread, I'm wondering — is there merit to considering async defer as a lone subset of scoped suspension points worth exploring on its own? I know that async defer is likely going to be intimately tied to a larger strategy regarding explicit/implicit suspensions, and I hesitate to post in the other thread because my use case is extremely tangential, but: are there any updated thoughts on whether this is something the team is interested in thinking through and implementing?

In the time since that thread, we've accepted async let with the implicit awaiting on cancellation on scope exit. So allowing async defer to introduce scope exit suspensions wouldn't be breaking new ground in that sense.

8 Likes

Ah, that's great news! What might the path forward look like, then? Is this something that might already be on a roadmap for the team, or would we need a pitch from the community? (I'd love to help contribute an implementation, though I still need to learn a lot more about the implementation details to reasonably pitch in.)

@Joe_Groff Any info related to @itaiferber's questions?

I've run into this a few times now when switching code to use/allow async. Without a finally equivalent we've sometimes been stuck doing things twice in a "normal" and "catch" path.

Just to be clear I have no interest in finally... defer is much nicer.

I don't know of any ongoing plans to implement async defer. It seems like a fine thing to develop as a pitch.

3 Likes

I'd love to help, if anyone wants to take the wheel. The more I‘m using concurrency, the more I‘m missing async defer.

2 Likes

I'm still trying to carve out a bit of spare time to get a pitch together, though if anyone makes it there first, I'm also happy to contribute!

3 Likes

@mickeyl @itaiferber I'd love to help too. Has anyone started drafting a proposal? I've been facing this at work multiple times now, and I really think it would be a very a nice addition to Swift

2 Likes

Unfortunately, I haven't had the opportunity to work on this, and it doesn't appear that I will any time soon. Although I can't take the initiative on this right now, if you (or someone else) can, I highly encourage the effort!

(Apologies to those interested in this that I can't be more helpful at the moment.)

Apologies if I'm reviving an old thread and a new one exists.

The lack of an async defer is currently biting me in a series of functions with 5+ different exit points where defer would be highly valuable, but the code I need to run from the defer is async. I can put a Task into the defer but that puts the execution onto another thread rather than executing before exiting the function on all exit paths.

It wasn't clear if this is still something being actively worked on, or what would need to be done to make this possible.

2 Likes

It does sound like an async let that you never await functions as async defer in practice if you ignore cancellation. That “ignore cancellation” part means it’s not a full answer, but maybe it’s a viable workaround?

It doesn't seem like it? async let starts the work immediately, an async defer would only start on an exit path. Unawaited async lets are automatically cancelled when leaving scope (I think), an async defer would allow either awaiting cancelling at the end of scope.

1 Like

*facepalm* Right, you'd have to do something even worse: a task that waits for cancellation before doing any work. Which I think you can contrive using Task.sleep(nanoseconds: veryBigNumber), but it's getting weird now.

Also cancelled Tasks themselves cannot very effectively do very much, because many of the functions they call will be "helpful" and check for cancellation and return immediately.

2 Likes

What I really would love to see is an async defer with the option to ignore cancellation in that scope. Imagine you are managing a resource which has an func shutdown() async. When your task gets cancelled the resource must be shutdown otherwise you would leak it on the system.

Right now the only way to achieve this is to spawn an unstructured Task and even then it might not be fully executed since your program might exit before the task completed.

I’m still very much an async novice, but I’m imagining this:

async let _cleanup = ({
  await Task.sleep(nanoseconds: .max)
  await Task {
    await actualCleanup()
  }
})()

and I think this avoids cancellation propagation while still doing priority donation (not from the initialization of the inner Task, but from awaiting it). But it’s a real mess, and a proper async defer wouldn’t have to piggyback on cancellation to work, which would allow the cleanup work to be cancelled as well. If that’s desirable.

1 Like

In Swift, unlike e.g. C#, it’s not the task itself that’s awaited. I’m pretty sure this would have to be await Task { … }.value.

1 Like

Ignoring the missing .value this does work and is the current way to have an enforced clean-up, but we have to resort to unstructured Concurrency which indicates that we are lacking a feature here. We ought to be able to express everything with structured Concurrency so that the compiler can reason about this.

Having a defer where we can opt-out of cancellation would be great.

4 Likes

Fun gotcha: In my experience, Task.sleep(nanoseconds:) returns immediately if you do that. Sleeping the current task next-to-forever seems to work with .max / 2 nanoseconds however. Maybe it's internally reinterpreting the UInt64 as signed?

(Also you probably mean to try? await that so it handles and ignores CancellationError.)

3 Likes