Structured concurrency with timers

It occurred to me that I don't recall see any proposal or discussion of using timers within a structured concurrency environment. Did I miss something, or is this MIA?

I see two basic use-cases, which I'll write in hypothetical Swift syntax:

  1. await elapsedTime(timeInterval), which would suspend the current async function and continue it after the specified time has elapsed.

  2. asyncAfter(timeInterval) { … }, which would execute the closure after the specified time, using the semantics of the proposed async { … } syntax.

These things can be done with existing APIs such as timers or GCD, but that make it awkward to get back to the same executor for code executed after the time interval.

That problem can be solved, I assume, with UnsafeContinuation, but without language or library support this particular wheel would have to be reinvented in every project.

Did I miss some discussion about this, or is this something we need to talk about?

5 Likes

This was the closest I’ve seen to an answer to this SE-0304: Structured Concurrency - #23 by ktoso . I don’t remember seeing it in any of the proposals.

1 Like

This feature exists today, you can use:

extension Task {
  /// Suspends the current task for _at least_ the given duration
  /// in nanoseconds.
  ///
  /// This function does _not_ block the underlying thread.
  public static func sleep(_ duration: UInt64) async { ... }
}

Source: swift/Task.swift at main · apple/swift · GitHub (the implementation is about to be fixed to not have to spawn a new task thanks to: [Concurrency] Reduce overhead of Task.yield and Task.sleep by drexin · Pull Request #37090 · apple/swift · GitHub).

In the original structured concurrency pitch (I see Doug removed the branch, so we don't have it online anymore) we wanted to also solve time and deadlines. Sadly time is known to be a very difficult API to get right and would take too much time to get right and ship structured concurrency this year, so we punted it. This function was in the proposals for quite some time, until the moment we had to pull back any APIs mentioning any form of time or deadline.

In any case, as we will re-investigate Task deadlines we will have to look into duration types. As we solve those, we will get a Duration type that we can use to make a nicer overload of the sleep function there.

You can use this sleep function until then though.

5 Likes

Thanks for the clarification. Using this function as a workaround for now is straightforward, but if it's going to be in the released concurrency implementation, it's a pity it didn't go through evolution discussion.

If it did, I'd object that "sleep" already has a meaning the C world, and this really isn't that. Are we sure we really want to make "sleep" a concept for Task, given that we already have a perfectly good verb that describes precisely what's going on: "suspend".

2 Likes

Because we're in a language with async/await, the callsite makes it pretty explicit it's going to suspend and not just block: await Task.sleep(...) which makes it pretty explicit: we await (suspend), it's a Task API, and it's "sleeping" for some time.

Please feel free to bring this to the structured concurrency API review if you feel strongly about it. We had numerous rounds of renaming it back and forth already though and this seems to have most buy in from the team.

2 Likes