[Second review] SE-0472: Starting tasks synchronously from caller context

Hello, Swift community!

After the first review of SE-0472: Starting tasks synchronously from caller context, the Language Steering Group returned the proposal for revision.

During the review, an enhancement was discussed that would remove the restriction that the task's closure could not be isolated to a different actor, and the Language Steering Group agreed with lifting that restriction. With that change in behavior, it was also requested that a new name be chosen for the API, since execution of the closure would no longer be "synchronous" if it had a different dynamic isolation from the caller.

A diff of the changes from the first version of the proposal can be found here.

The second review of this proposal begins now and runs through May 19th.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager by DM. When contacting the review manager directly, please put "SE-0472" in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/swiftlang/swift-evolution/blob/main/process.md

Thank you,
Tony Allevato
Review Manager

3 Likes

Overall I'm +1 on the proposal with the latest changes. I think this solves an important use-case for UI applications and in general makes unstructured tasks better to deal with. I have some clarifying questions on the proposal though.

Suspension points

After a suspension happens the task will resume on the the executor as implied by the task operation's isolation, as would be the case normally.

Upon first suspension inside the immediate task, the calling executor is freed up and able to continue executing other work, including the code surrounding the creation of the immediate task. This happens specifically when when a real suspension happens, and not for "potential suspension point" (which are marked using the await keyword)

These two sections are talking about suspensions but the proposal isn't clear what definition of suspension it is using. Often people are thinking about suspensions when a continuation is awaited. However, there is also the concept of enqueueing a task's job on the executor which can be seen as a potential suspension since another task can interleave. The latter often happens with isolation and task executors. This might be outside of this proposal but it might be good to officially define what a suspension point is at some point.

Example of an isolation change based suspension
// Module A
@MainActor
func foo() {
  Task { @MainActor in
      MainActor.assertIsolated()
      print("Unstructured task")
      MainActor.assertIsolated()
  }
  Task.startSynchronously {
      MainActor.assertIsolated()
      print("Immediate unstructured task started")
      await yield()
      MainActor.assertIsolated()
      print("Immediate unstructured task finished")
  }
}

// Module B
public func yield() async {
    print("yield")
}

The above prints

Immediate unstructured task started
Unstructured task
yield
Immediate unstructured task finished

@isolated(any) closures

The newly proposed APIs don't mark their closures as @isolated(any). What's the reason for that? It wasn't entirely clear to me why those closures aren't annotated.

@discardableResult tasks

It was previously brought up, that the current Task.init and Task.detached methods are marked as @discardableResult that often lead to developers not storing those unstructured tasks which easily leads to unexpected behavior. Especially when the Failure is not Never then the failure will never be handled. With this proposal we are introducing another set of APIs that follow the same pattern. Should we reconsider the @discardableResult on those new APIs?

1 Like

I am +1 with the latest set of changes.

I roughly followed the previous review and the changes to it in this iteration are a big improvement in my opinion. I feel like it is much more clear on intent and usage.