[swift-evolution] [Concurrency] async/await + actors

Adam Kemp adam.kemp at apple.com
Mon Aug 21 13:04:30 CDT 2017



> On Aug 18, 2017, at 8:38 PM, Chris Lattner <clattner at nondot.org <mailto:clattner at nondot.org>> wrote:
> 
> On Aug 18, 2017, at 2:09 PM, Adam Kemp <adam.kemp at apple.com <mailto:adam.kemp at apple.com>> wrote:
>> Maybe I’m still missing something, but how does this help when you are interacting only with Swift code? If I were to write an asynchronous method in Swift then how could I do the same thing that you propose that the Objective-C importer do? That is, how do I write my function such that it calls back on the same queue?
> 
> You’re right: if you’re calling something written in Swift, the ObjC importer isn’t going to help you.
> 
> However, if you’re writing an async function in Swift, then it is reasonable for us to say what the convention is and expect you to follow it.  Async/await doesn’t itself help you implement an async operation: it would be turtles all the way down… until you get to GCD, which is where you do the async thing.
> 
> As such, as part of rolling out async/await in Swift, I’d expect that GCD would introduce new API or design patterns to support doing the right thing here.  That is TBD as far as the proposal goes, because it doesn’t go into runtime issues.

The point I’m trying to make is that this is so important that I don’t think it’s wise to leave it up to possible future library improvements, and especially not to convention. Consider this example again from your proposal:

@IBAction func buttonDidClick(sender:AnyObject) {  
    doSomethingOnMainThread();
    beginAsync {
        let image = await processImage()
        imageView.image = image
    }
    doSomethingElseOnMainThread();
}

The line that assigns the image to the image view is very likely running on the wrong thread. That code looks simple, but it is not safe. You would have to insert a line like your other examples to ensure it’s on the right thread:

@IBAction func buttonDidClick(sender:AnyObject) {  
    doSomethingOnMainThread();
    beginAsync {
        let image = await processImage()
        await DispatchQueue.main.asyncCoroutine()
        imageView.image = image
    }
    doSomethingElseOnMainThread();
}

You would have to litter your code with that kind of stuff just in case you’re on the wrong thread because there’s no way to tell where you’ll end up after the await. In fact, this feature would make it much easier to end up calling back on different queues in different circumstances because it makes queue hopping invisible. From another example:

func processImageData1() async -> Image {
  let dataResource  = await loadWebResource("dataprofile.txt")
  let imageResource = await loadWebResource("imagedata.dat")
  let imageTmp      = await decodeImage(dataResource, imageResource)
  let imageResult   =  await dewarpAndCleanupImage(imageTmp)
  return imageResult
}

Which queue does a caller end up in? Whichever queue that last awaited call gives you. This function does nothing to try to ensure that you always end up on the same queue. If someone changes the code by adding or removing one of those await calls then the final callback queue would change. If there were conditionals in there that changed the code flow at runtime then you could end up calling back on different queues depending on some runtime state.

IMO this would make doing safe async programming actually more difficult to get right. It would be tedious and error prone. This simplified async/await model may work well for JavaScript, which generally doesn’t have shared mutable state across threads, but it seems dangerous in a language that does.

> This isn’t a fair transformation though, and isn’t related to whether futures is part of the library or language.  The simplification you got here is by making IBAction’s implicitly async.  I don’t see that that is possible, since they have a very specific calling convention (which returns void) and are invoked by objc_msgSend.  OTOH, if it were possible to do this, it would be possible to do it with the proposal as outlined.

I didn’t mean to imply that all IBActions implicitly async. I just allowed for an entire method to be async without being awaitable. In C# an async void function is a “fire and forget” function. It executes in the context of the caller’s thread/stack up until the first await, at which point it returns to the caller like normal. The continuation just happens without the caller knowing about it. The method signature is the same, and they are callable by code that is unaware of async/await. C# supports async void functions specifically for the event handler use case (and it is generally discouraged for all other use cases).

Your proposal already has async void methods, but they are awaitable. You still need some ability to call an async method from a non-async method. The way that you solved that is a special function (beginAsync), which as I described earlier has some issues with readability. The other approach is to have some way to decorate the entire function as “fire and forget”.

The reason I linked these to library support is that the way that C# makes this distinction is that an awaitable function must return a type that is awaitable (it’s actually the traits of the return type that make it awaitable, not the async keyword; you can await a function that is not marked async). In C# a void method is not awaitable, even if it is marked async.

However, you’re right that these concepts don’t necessarily have to be linked, and I shouldn’t have conflated them. You could also use some other syntax to mark a non-awaitable method that is itself async. Maybe a different keyword (deliberately ugly):

@IBAction fire_and_forget_async func buttonDidClick(sender:AnyObject) { … }

To be clear, although this particular problem can be solved without library support I think that both this problem and the queue-hopping problem could be better solved by using a model closer to C#’s, with a richer compiler transform that uses the return type. Swift could do this using an awaitable protocol.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170821/1df1bfc5/attachment.html>


More information about the swift-evolution mailing list