[Accepted with Modification] SE-0296: async/await

Great example. Thanks. I wasn't sure if the compiler was magically going to reach into the function and find return values or what.

This syntax is very similar to that used in RxSwift when doing something like Single.create { }.

Although, true to form, it's pretty deep into standardSwiftRunOnSentence-land (withUnsafeThrowingContinuation indeed!).

Umm... if you don't mind my asking, why is the continuation considered unsafe? Because it may never be called?

The api docs have this line:

The operation functions must resume the continuation exactly once .

This is up to the consumers of the api and isn't enforced by the compiler, which I would assume is why they are labeled as unsafe.

3 Likes

Yes, that's exactly right. If you resume the continuation multiple times, the program will probably crash; if you don't resume it, the task and its parent tasks will hang forever and any resources tied up with them will leak.

3 Likes

That "unsafe" as part of the function signature feels unnecessary and just clutters up the code. Almost any API is "unsafe" if misused or passed invalid parameters.

FYI, this exact API is under review right now as SE-0300.

Doug

3 Likes
  • Case 1:
func doSomething() -> String { ... }

async version:

async func doSomething() -> String { ... }

It just a syntactic sugar for Future<T> :

func doSomething() -> Future<String> { ... }

// lambda or closure

let doSomething_Async_Closure: Future<String> = async {
       // return getStringFuture<String>().await()
      return await getStringFuture<String>()    // syntactic sugar for `Future<String>.await()`
}

let doSomething_Async_Closure2: Future<String> = async { () -> String in
       // return getStringFuture<String>().await()
      return await getStringFuture<String>()    // syntactic sugar for `Future<String>.await()`
}

Unfortunately, { async in ... } already has a meaning as a closure that has a single parameter that we name async . It might not happen in practice, but another syntactic micro-optimization on closure syntax that conflicts with an existing syntactic micro-optimization doesn't seem worthwhile to me.

// also solve the ploblem describe in above quote:
let doSomething_Closure2 = async { async -> String in
      // return getStringFuture<String>().await()
      return await getStringFuture<String>()    // syntactic sugar for `Future<String>.await()`
}

and avoid the async in function signature in closure

{ () async -> Int in      // <----  here
  print("here")
  return await getIntFuture()
}
  • Case 2:
func doSomething() throws -> String { ... }

as we known, it just a syntactic sugar for Result<T,E> in swift stdlib:

func doSomething() -> Result<String, Error> { ... }

async version:

async func doSomething() throws -> String { ... }

It just an other syntactic sugar for Future<T> :

func doSomething() -> Futrue<Result<String, Error>> { ... }

// usage:
let usage_Closure = async { () throws in
      let result:  Result<String, Error> = await doSomething()
      let str: String =  try result.get()
      return  str
}

// equals to

let usage_Closure2 = async { () throws in
     return try await doSomething()
}

Note:

It is

try await

not

 await try
  • Case 3:

What is

async { 
...
}

?

It is a init function in Futrue<T> :

class Future<T> {
   public init(body: ()  -> T)
}

So

async func doSomething() throws -> String { ... // dosth }

// and

let future: Future<Result<String, Error>> = async {
    ... // dosth
}

It is just another syntactic sugar:

func doSomething() throws -> String { ... }

let future: Future<Result<String, Error>> = Future.init {
     return Result {
           try doSomething() 
     }
}
  • Case 4

0296-async-await.md#autoclosures

func computeArgumentLater<T>(_ fn: @escaping @autoclosure () async -> T) { } 

as Case 1 quote:

Unfortunately, { async in ... } already has a meaning as a closure that has a single parameter that we name async . It might not happen in practice, but another syntactic micro-optimization on closure syntax that conflicts with an existing syntactic micro-optimization doesn't seem worthwhile to me.

maybe more better like below:

func computeArgumentLater<T>(_ fn: @escaping @autoclosure async () -> T) { } 

or 

func computeArgumentLater<T>(_ fn: @escaping @autoclosure @async () -> T) { } 
  • Case 5:

Support Thread Switch, it's inspiration from Arrow Fx 's continueOn a kotlin FP framework:

let queue = DispatchQueue.global()
let queue_001 = DispatchQueue(label: "queue_001", attributes: .concurrent)
let queue_002 = DispatchQueue(label: "queue_002", attributes: .concurrent)

async func doSomething() throws -> String {
       continueOn(queue_001)
        print("continueOn - queue_001 -  \(Thread.current)")

        // do some IO  job on queue_001
        let textFromDisk: String = await doSomeIOJob()

        continueOn(DispatchQueue.main)
        print("continueOn - queue_main -  \(Thread.current)")

        // update label on UI Thread (Main Thread)
        label.setText(textFromDisk)

        continueOn(queue_002)
        print("continueOn - queue_002 -  \(Thread.current)")
        
        // do some net requests on queue
        let httpResponse = await httpClient.post("https://www.google.com/blogs")

       continueOn(queue)
}

It's the most advanced feature only be supported in kotlin-coroutines. C# doesn't have that functional.

One of idea is like below:

// if below make sense

class Future<T {
    init(scheduler: DispatchQueue, task: () -> T)
}
async(queue: DispatchQueue) { ... }


// now you can 
async func doSomething() throws -> String {
        let textFromDisk: String = await async(queue_001) {
              // do some IO job
       }

       await async(DispatchQueue.main) {
              // update UI
              label.setText(textFromDisk)
       }

      // ....
}

and below code is ugly if () async -> T

let queue_001 = DispatchQueue(label: "queue_001", attributes: .concurrent)

func doFirstThing() async(queue_001) throws -> String {   // <----- ??

}

let doSecondThing = { () async(DispatchQueue.main) throws -> String in    //  <----- ??
    return "second"
}

func doSomething() async throws -> String {
       try await doFirstThing()
       
       try await doSecondThing()
}
1 Like

When I was trying out async/await I saw that we have a function with the name withUnsafeContinuation which we use with await keyword to wait for an operation. May I know why is the word unsafe used in a function name here? What's so unsafe about it?

Continuation is in a separated proposal–SE-0300.

You need to resume the continuation exactly once, which isn't guaranteed by the language. Otherwise, it would lead to memory issue (unsafe).

2 Likes

I was exploring the async/await feature using the March 25th development snapshot to call an API with URLSession where I got the following crash and I was not able to figure out what's causing this

I tried couple of things but was not able to resolve this, given below is my code for calling the API

func testFunction() async -> [Project]?
{
    var urlRequest = URLRequest(url: URL(string: "MY_API")!)
    urlRequest.httpMethod = "get"

    return await withUnsafeContinuation { continuation in
        let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
            if(error == nil && data != nil && data?.count != 0) {
                do {
                    let decoder = JSONDecoder()
                    // for date formatting
                    decoder.dateDecodingStrategy = .iso8601
                    let result = try decoder.decode([Project].self, from: data!)
                    continuation.resume(returning: result)
                } catch  {
                    debugPrint(error.localizedDescription)
                }
            }
        }
        task.resume()
    }
}

and I have a separate structure with a function having the @asyncHandler attribute where I am calling this async function

struct SyncResource
{
   @asyncHandler func syncDataResources()
{
    let projectObj = ProjectDataResource()
    let projects = await projectObj.testFunction()
}

and in the viewDidLoad function of the viewController I am creating the object of the SyncResource struct and calling the syncDataResource function where I encountered the above crash, anyone else facing the same issue, or am I doing something wrong here?

I am not getting any compile or build errors I even added the concurrency flag as mentioned in the proposal

That does look like a bug. I know we've fixed some bugs with async closures recently, but it's hard to verify that your bug specifically is fixed without a complete test case. Would you mind filing a bug over at https://bugs.swift.org?

Hello John, thank you for your response I have logged the bug [SR-14468] and have attached my project for reference.

6 Likes