[Concurrency] async/await + actors

This has been a long awaited topic so I'm really excited to see the
conversation start!
After reading the manifesto and the proposal there is one thing that
keeps wondering in my head, and is making async implicitly throwing.
It the common case it makes sense, but there is a part of the proposal
that talks about "yields" and how the implementation is "coroutines"
that can be used for other things not only for concurrent operations.
In that case, wouldn't make more sense to keep throwing separated?

···

On Fri, Aug 18, 2017 at 2:17 PM, Thomas via swift-evolution <swift-evolution@swift.org> wrote:

I have been writing a lot of fully async code over the recent years (in objc) and this all seems to fit well with what we're doing and looks like it would alleviate a lot of the pain we have writing asyc code.

# Extending the model through await

I'm a bit worried about the mention of dispatch_sync() here (although it may just be there to illustrate the deadlock possibility). I know the actor runtime implementation is not yet defined, but just wanted to mention that dispatch_sync() will lead to problems such as this annoying thing called thread explosion. This is why we currently cannot use properties in our code (getters would require us to call dispatch_sync() and we want to avoid that), instead we are writing custom async getters/setters with callback blocks. Having async property getters would be pretty awesome.

Another thing: it is not clearly mentionned here that we're getting back on the caller actor's queue after awaiting on another actor's async method.

# Scalable Runtime

About the problem of creating too many queues. This is something that has annoyed me at this year's wwdc. It used to be back when the libdispatch was introduced in 10.6 that we were told that queues are very cheap, we could create thousands of them and not worry about threads, because the libdispatch would do the right thing internally and adjust to the available hardware (the number of threads would more or less match the number of cores in your machine). Somehow this has changed, now we're being told we need to worry about the threads behind the queues and not have too many of them. I'm not sure if this is something inevitable due to the underlying reality of the system but the way things were presented back then (think in term of queues, don't worry about threads) was very compelling.

# Entering and leaving async code

Certainly seems like the beginAsync(), suspendAsync() primitives would be useful outside of the stdlib. The Future<T> example makes use of suspendAsync() to store the continuation block and call it later, other codes would do just as well.

Shouldn't this:

let imageTmp = await decodeImage(dataResource.get(), imageResource.get())

rather be:

let imageTmp = await decodeImage(await dataResource.get(), await imageResource.get())

Thomas

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Alejandro Martinez

I think that awaiting on the result of an actor method ends up being pretty similar (in terms of implementation and design tradeoffs) as dispatch_sync. That said, my understanding is that thread explosion in GCD happens whenever something blocks a GCD thread, not when it politely yields control back to GCD. Am I misunderstanding what you mean.

dispatch_sync isn't quite the ideal way of thinking about it, since it will block the calling context, and as you note this would potentially deadlock the current thread if an actor invokes one of its own methods.

As well as more perniciously creating deadlock opportunities if there are cycles in actors dispatching to each other. It really has to be a "re-queue when there's a response" model.

John.

···

On Aug 18, 2017, at 3:11 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Aug 18, 2017, at 11:57 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

This isn't a desirable or fundamentally necessary pitfall, since really, the caller actor is suspending in wait simultaneously with the callee actor taking control. This is more like a "tail-dispatch_async" kind of operation, where you don't really want the calling context to block, but you still want to be able to reuse the current thread since it'll be immediately freed up by blocking on the async operation. That's something we could conceivably build into runtime support for this model.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

disptach_sync() calls will cause the GCD thread to block (waiting for the queue to be available). I'm pretty sure this causes libdispatch to spawn more threads if needed (for example if dispatch_async() calls are made in other parts of the program), up to thread explosion.

Thomas

···

On 18 Aug 2017, at 20:57, Chris Lattner <clattner@nondot.org> wrote:

I'm a bit worried about the mention of dispatch_sync() here (although it may just be there to illustrate the deadlock possibility). I know the actor runtime implementation is not yet defined, but just wanted to mention that dispatch_sync() will lead to problems such as this annoying thing called thread explosion. This is why we currently cannot use properties in our code (getters would require us to call dispatch_sync() and we want to avoid that), instead we are writing custom async getters/setters with callback blocks. Having async property getters would be pretty awesome.

I think that awaiting on the result of an actor method ends up being pretty similar (in terms of implementation and design tradeoffs) as dispatch_sync. That said, my understanding is that thread explosion in GCD happens whenever something blocks a GCD thread, not when it politely yields control back to GCD. Am I misunderstanding what you mean.

You seem to imply we are able to jump on the callee actor queue immediately, but the queue may not be available (the actor could be processing another message). So I'm not sure how it goes in term of reusing the same thread, maybe there's some clever trick to do this if it can be determined that the queue is immediately available. But using dispatch_sync() wouldn't work for me as it would potentially block the caller GCD thread and that could end up with thread explosion.

Thomas

···

On 18 Aug 2017, at 21:11, Joe Groff <jgroff@apple.com> wrote:

On Aug 18, 2017, at 11:57 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

I think that awaiting on the result of an actor method ends up being pretty similar (in terms of implementation and design tradeoffs) as dispatch_sync. That said, my understanding is that thread explosion in GCD happens whenever something blocks a GCD thread, not when it politely yields control back to GCD. Am I misunderstanding what you mean.

dispatch_sync isn't quite the ideal way of thinking about it, since it will block the calling context, and as you note this would potentially deadlock the current thread if an actor invokes one of its own methods. This isn't a desirable or fundamentally necessary pitfall, since really, the caller actor is suspending in wait simultaneously with the callee actor taking control. This is more like a "tail-dispatch_async" kind of operation, where you don't really want the calling context to block, but you still want to be able to reuse the current thread since it'll be immediately freed up by blocking on the async operation. That's something we could conceivably build into runtime support for this model.

Sent from my iPad

For instance, has Array<UIView> value semantics?

By the commonly accepted definition, Array<UIView> does not provide value semantics.

You might be tempted to say that it does not because it contains class references, but in reality that depends on what you do with those UIViews.

An aspect of the type (“does it have value semantics or not”) should not depend on the clients. By your definition, every type has value semantics if none of the mutating operations are called :-)

No, not mutating operations. Access to mutable memory shared by multiple "values" is what breaks value semantics. You can get into this situation using pointers, object references, or global variables. It's all the same thing in the end: shared memory that can mutate.

For demonstration's sake, here's a silly example of how you can give Array<Int> literally the same semantics as Array<UIView>:

  // shared UIView instances in global memory
  var instances: [UIView] =

  extension Array where Element == Int {

    // append a new integer to the array pointing to our UIView instance
    func append(view: UIView) {
      self.append(instances.count)
      instances.append(newValue)
    }

    // access views pointed to by the integers in the array
    subscript(viewAt index: Int) -> UIView {
      get {
        return instances[self[index]]
      }
      set {
        self[index] = instances.count
        instances.append(newValue)
      }
    }
  }

And now you need to worry about passing Array<Int> to other thread. ;-)

It does not really matter whether the array contains pointers or wether it contains indices into a global table: in both cases access to the same mutable memory is accessible through multiple copies of an array, and this is what breaks value semantics.

Types cannot enforce value semantics. Its the functions you choose to call that matters. This is especially important to realize in a language with extensions where you can't restrict what functions gets attached to a type.

This gets deeper into the territory of the conversation Dave A and I had a while ago. I think this conflates value semantics with pure functions, which I think is a mistake.

I agree that if you assume away reference counting a function that takes Array<UIView> but never dereferences the pointers can still be a pure function. However, I disagree that Array<UIView> has value semantics.

The relationship of value semantics to purity is that value semantics can be defined in terms of the purity of the "salient operations" of the type - those which represent the meaning of the value represented by the type. The purity of these operations is what gives the value independence from copies in terms of its meaning. If somebody chooses to add a new impure operation in an extension of a type with value semantics it does not mean that the type itself no longer has value semantics. The operation in the extension is not "salient".

This still begs the question: what operations are "salient"? I think everyone can agree that those used in the definition of equality absolutely must be included. If two values don't compare equal they clearly do not have the same meaning. Thread safety is also usually implied for practical reasons as is the case in Chris's manifesto. These properties are generally considered necessary for value semantics.

While these conditions are *necessary* for value semantics I do not believe they are *sufficient* for value semantics. Independence of the value is also required. When a reference type defines equality in terms of object identity copies of the reference are not truly independent.

This is especially true in a language like Swift where dereference is implicit. I argue that when equality is defined in terms of object identity copies of the reference are *not* independent. The meaning of the reference is inherently tied up with the resource it references. The resource has to be considered "salient" for the independence to be a useful property. On the other hand, if all you really care about is the identity and not the resource, ObjectIdentifier is available and does have value semantics. There is a very good reason this type exists.

The reason we're discussing value semantics here is because they are useful making concurrency safer. If we define the meaning of value semantics as "a type where a subset of the member functions (the important ones) can be used with concurrency" then that definition of value semantics lose quite a bit of its value for solving the problem at hand. It's too vague.

I'm not actually that interested in the meaning of value semantics here. I'm debating the appropriateness of determining whether something can be done in another thread based on the type a function is attached to. Because that's what the ValueSemantical protocol wants to do. ValueSemantical, as a protocol, is whitelisting the whole type while in reality it should only vouch for a specific set of safe functions on that type.

This is making some assumptions about the semantics of `ValueSemantical` and the compiler verification behind it, but I see what you're saying. It sounds to me like you're looking for a level of safety beyond what Chris is trying to tackle in his manifesto. My impression is that his goal is to make a pragmatic set of tradeoffs.

As an example to consider, we could write an extension method on `Int` that accesses some global state. It may use the value of the `Int` as an index into a global array. The existence and use of this method is completely orthogonal to whether or not it is safe to pass the `Int` as part of a message to an actor. The danger is in the use of the operation, not the source of the `Int`.

As I understand it, the intent of `ValueSemantical` is to avoid passing *access* to shared mutable state as part of a message to an actor. Passing an `Int` value does not give the actor any access it didn't already have. On the other hand, passing a `UIView` gives it access to shared mutable state it did not have before receiving the message.

Achieving the kind of safety you're looking for would require verification that an actor has exclusive access to all mutable state it uses (including state that is used by all functions it calls). This would be wonderful, but would also preclude a lot of very useful things, most importantly C-interop. Here is the relevant portion of Chris's manifesto regarding global mutable state and the tradeoffs he believes will be pragmatic and effective in practice:

Since we're friends, I'll be straight with you: there are no great answers here. Swift and C already support global mutable state, so the best we can do is discourage the use of it. We cannot automatically detect a problem because actors need to be able to transitively use random code that isn't defined on the actor. For example:

func calculate(thing : Int) -> Int { ... }

actor Foo {
  actor func exampleOperation() {
     let x = calculate(thing: 42)
     ...
  }
}
There is no practical way to know whether 'calculate' is thread-safe or not. The only solution is to scatter tons of annotations everywhere, including in headers for C code. I think that would be a non-starter.

In practice, this isn't as bad as it sounds, because the most common operations that people use (e.g. print) are already internally synchronizing, largely because people are already writing multithreaded code. While it would be nice to magically solve this long standing problem with legacy systems, I think it is better to just completely ignore it and tell developers not to define or use global variables (global lets are safe).

All hope is not lost though: Perhaps we could consider deprecating global vars from Swift to further nudge people away from them. Also, any accesses to unsafe global global mutable state from an actor context can and should be warned about. Taking some steps like this should eliminate the most obvious bugs.

···

Sent from my iPad

On Aug 19, 2017, at 3:29 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 19 août 2017 à 11:38, Matthew Johnson <matthew@anandabits.com> a écrit :
On Aug 19, 2017, at 8:16 AM, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

--
Michel Fortin
https://michelf.ca

To state more explicitly what I think you might be implying here: In principle, we could have developers annotate value-semantic *members* instead of value-semantic *types* and only allow value-semantic members to be used on parameters to an actor. But I worry this might spread through the type system like `const` in C++, forcing large numbers of APIs to annotate parameters with `value` and restrict themselves to value-only APIs just in case they happen to be used in an actor.

···

On Aug 19, 2017, at 1:29 PM, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

I'm not actually that interested in the meaning of value semantics here. I'm debating the appropriateness of determining whether something can be done in another thread based on the type a function is attached to. Because that's what the ValueSemantical protocol wants to do. ValueSemantical, as a protocol, is whitelisting the whole type while in reality it should only vouch for a specific set of safe functions on that type.

--
Brent Royal-Gordon
Architechies

Regardless of which approach we take, it feels like something that needs to be implicit for structs and enums where value semantics is trivially provable by way of transitivity. When that is not the case we could require an explicit `value` or `nonvalue` annotation (specific keywords subject to bikeshedding of course).

There is no such thing as "trivially provable by way of transitivity". This type is comprised of only value types, and yet it has reference semantics:

  struct EntryRef {
    private var index: Int
    
    var entry: Entry {
      get { return entries[index] }
      set { entries[index] = newValue }
    }
  }

This type uses global mutable state in its implementation. This is not hard for the compiler to detect and is pretty rare in most code.

This type is comprised of only reference types, and yet it has value semantics:

  struct OpaqueToken: Equatable {
    class Token {}
    private let token: Token
    
    static func == (lhs: OpaqueToken, rhs: OpaqueToken) -> Bool {
      return lhs.token === rhs.token
    }
  }

Yes, of course this is possible. I believe this type should have to include an annotation declaring value semantics and should also need to annotate the `token` property with an acknowledgement that value semantics is being preserved by the implementation of the type despite this member not having value semantics. The annotation on the property is to prevent bugs that might occur because the programmer didn't realize this type does not have value semantics.

I think it's better to have types explicitly declare that they have value semantics if they want to make that promise, and otherwise not have the compiler make any assumptions either way. Safety features should not be *guessing* that your code is safe. If you can somehow *prove* it safe, go ahead—but I don't see how that can work without a lot of manual annotations on bridged code.

I agree with you that *public* types should have to declare that they have value semantics. And I'm not suggesting we attempt to *prove* value semantics everywhere.

I'm suggesting that the proportion of value types in most applications for which we can reasonably infer value semantics is pretty large. If the stored properties of a value type all have value semantics and the implementation of the type does not use global mutable state it has value semantics.

Whether we require annotation or not, value semantics will be decided by the declaring module. If we don't infer it we'll end up having to write `value struct` and `value enum` a lot. The design of Swift has been vigorous in avoiding keyword soup and I really believe that rule applies here. The primary argument I can think of against inferring value semantics for non-public value types in these cases is if proving a type does not use global mutable state in its implementation would have too large an impact on build times.

···

Sent from my iPad

On Aug 19, 2017, at 9:33 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Aug 19, 2017, at 7:41 AM, Matthew Johnson <matthew@anandabits.com> wrote:

--
Brent Royal-Gordon
Architechies

It seems like the answer should be "yes". But then how do you implement something like a cancel() method? I don't know how the actor model solves that problem.

Thomas

···

On 20 Aug 2017, at 03:36, Brent Royal-Gordon <brent@architechies.com> wrote:

On Aug 19, 2017, at 2:25 AM, Thomas <tclementdev@free.fr <mailto:tclementdev@free.fr>> wrote:

I think we need to be a little careful here—the mere fact that a message returns `Void` doesn't mean the caller shouldn't wait until it's done to continue. For instance:

  listActor.delete(at: index) // Void, so it doesn't wait
  let count = await listActor.getCount() // But we want the count *after* the deletion!

In fact this will just work. Because both messages happen on the actor's internal serial queue, the "get count" message will only happen after the deletion. Therefore the "delete" message can return immediately to the caller (you just need the dispatch call on the queue to be made).

Suppose `delete(at:)` needs to do something asynchronous, like ask a server to do the deletion. Is processing of other messages to the actor suspended until it finishes? (Maybe the answer is "yes"—I don't have experience with proper actors.)

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.

···

On Aug 18, 2017, at 8:38 PM, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:
On Aug 18, 2017, at 2:09 PM, Adam Kemp <adam.kemp@apple.com <mailto:adam.kemp@apple.com>> wrote:

Because async/await serve a different (but related) purpose than background threads. Remember that concurrency is not parallelism: running in parallel in the background is a useful construction, but not the *only* construction.

If you have multiple tasks you’d like to kick off in parallel, async/await can support that by providing some kind of awaitable Future object. You’d then call multiple functions that return those futures and then await on the completion of all of the Futures (Future.gatherResults is a nice function name).

However, async/await also allows you to more directly handle the notion that you are making a single blocking call that you need the result of before your computation can complete. For example, if you only had to load *one* web resource instead of two, your code with async/await would look exactly like synchronous code with the word ‘await’ scattered around:

func doit() async throws {
    let dataResource = await loadWebResource(“dataprofile.txt”)
    let imageTmp = decodeImage(dataResource)
    return dewarpAndCleanupImage(imageTmp)
}

At this point scheduling this function becomes the calling programmer’s concern. It also allows multiple calls to doit() to be interleaved without deferring them to background threads and relying on the OS scheduler to schedule them appropriately. This is very useful on server-side applications that do not want to be dispatching large numbers of threads, and that are likely already running an I/O select() loop that can handle the most common cause of awaits (namely, please do this I/O).

For functions that are computationally heavy, no problem: most languages provide built-in support for scheduling async functions into threadpools and providing a Future that completes on the completion of the background task.

I guess the TL;DR here is that async/await allows you to have concurrency with or without parallelism, while using thread pools means you can only have concurrency with parallelism.

Cory

···

On 11 Sep 2017, at 00:10, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

Not really certain what async/await adds, using this library (note self promotion) which is built on top of GCD:

GitHub - hlovatt/Concurrency-Utilities: Atomics, Futures, and Reactive-Streams for Swift

You can write:

    func doit() {
        AsynchronousFuture { // Executes in background and therefore does not block main
            let dataResource = loadWebResource("dataprofile.txt") // Returns a future and therefore runs concurrently in background.
            let imageResource = loadWebResource("imagedata.dat") // Future therefore concurrent.
            let imageTmp = decodeImage(dataResource.get ?? defaultText, imageResource.get ?? defaultData) // Handles errors with defaults easily, including timeout.
            let imageResult = dewarpAndCleanupImage(imageTmp)
            Thread.executeOnMain {
                self.imageResult = imageResult
            }
        }
    }

So why bother with async/await?

Yeah, "tail-async" would be something the runtime needs to be able to attempt conditionally. If either the sending context doesn't immediately return to the scheduler, or the receiving context is blocked (possibly with some grace period), then you might transparently degrade to a regular async.

-Joe

···

On Aug 18, 2017, at 12:40 PM, Thomas <tclementdev@free.fr> wrote:

On 18 Aug 2017, at 21:11, Joe Groff <jgroff@apple.com> wrote:

On Aug 18, 2017, at 11:57 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

I think that awaiting on the result of an actor method ends up being pretty similar (in terms of implementation and design tradeoffs) as dispatch_sync. That said, my understanding is that thread explosion in GCD happens whenever something blocks a GCD thread, not when it politely yields control back to GCD. Am I misunderstanding what you mean.

dispatch_sync isn't quite the ideal way of thinking about it, since it will block the calling context, and as you note this would potentially deadlock the current thread if an actor invokes one of its own methods. This isn't a desirable or fundamentally necessary pitfall, since really, the caller actor is suspending in wait simultaneously with the callee actor taking control. This is more like a "tail-dispatch_async" kind of operation, where you don't really want the calling context to block, but you still want to be able to reuse the current thread since it'll be immediately freed up by blocking on the async operation. That's something we could conceivably build into runtime support for this model.

You seem to imply we are able to jump on the callee actor queue immediately, but the queue may not be available (the actor could be processing another message). So I'm not sure how it goes in term of reusing the same thread, maybe there's some clever trick to do this if it can be determined that the queue is immediately available. But using dispatch_sync() wouldn't work for me as it would potentially block the caller GCD thread and that could end up with thread explosion.

Ok, my misunderstanding. That does make it a bad analogy, I’ll update the proposal.

-Chris

···

On Aug 18, 2017, at 12:23 PM, Thomas <tclementdev@free.fr> wrote:

On 18 Aug 2017, at 20:57, Chris Lattner <clattner@nondot.org> wrote:

I'm a bit worried about the mention of dispatch_sync() here (although it may just be there to illustrate the deadlock possibility). I know the actor runtime implementation is not yet defined, but just wanted to mention that dispatch_sync() will lead to problems such as this annoying thing called thread explosion. This is why we currently cannot use properties in our code (getters would require us to call dispatch_sync() and we want to avoid that), instead we are writing custom async getters/setters with callback blocks. Having async property getters would be pretty awesome.

I think that awaiting on the result of an actor method ends up being pretty similar (in terms of implementation and design tradeoffs) as dispatch_sync. That said, my understanding is that thread explosion in GCD happens whenever something blocks a GCD thread, not when it politely yields control back to GCD. Am I misunderstanding what you mean.

disptach_sync() calls will cause the GCD thread to block (waiting for the queue to be available). I'm pretty sure this causes libdispatch to spawn more threads if needed (for example if dispatch_async() calls are made in other parts of the program), up to thread explosion.

Based on these same concerns, how to do this using async/await ?

func process() -> Void) {

    loadWebResource("bigData.txt") { dataResource in
    //....
    }

    printf("BigData Scheduled to load")
    loadWebResource("smallData.txt") { dataResource in
    //....
    }
    printf("SmallData Scheduled to load")

}

Small data usually will finish first using GCD, but using async/await apear
to be no way to schedule other things (on other Queue maybe) in the same
function. I now this is about parallelism, but this is something that we
will lose about GCD.

I know the parallelism is not the focus, but is something important for the
people migration from GCD.

Maybe:

yield loadWebResource("bigData.txt") // has a void return.

yield loadWebResource("smallData.txt") // has a void return.

I don't know if yield is the right keyword here but i think is important
some way to "not" wait.

···

Em seg, 21 de ago de 2017 Ă s 15:04, Adam Kemp via swift-evolution < swift-evolution@swift.org> escreveu:

On Aug 18, 2017, at 8:38 PM, Chris Lattner <clattner@nondot.org> wrote:

On Aug 18, 2017, at 2:09 PM, Adam Kemp <adam.kemp@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.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I found a decent description about async/await here:

So it’s much more like runloop based callbacks in Foundation that libdispatch. It’s complicated. Even the simplest example is super complicated.

It’s super implicit (like, “I’m going to package up every line of code from await to the end of this function and turn it into a continuation”). That seems to go against one of the primary principles of Swift, which is to make things plain to the reader. I’d be interested to know what the call stack looks like on the line after await.

The doc takes away some of the mystery, but leaves major questions, like: await is used to yield control to the parent, but at the bottom of the call stack, presumably you’re going to do something blocking, so how do you call await?

-Kenny

Processing of other messages to the actor should be suspended until `delete(at:)` finishes. Otherwise the actor's state would not be protected properly. But obviously this does not help if `delete(at:)` itself delegates the deletion to another actor with a fire-and-forget message.

-Thorsten

···

Am 20.08.2017 um 04:38 schrieb Thomas via swift-evolution <swift-evolution@swift.org>:

On 20 Aug 2017, at 03:36, Brent Royal-Gordon <brent@architechies.com> wrote:

On Aug 19, 2017, at 2:25 AM, Thomas <tclementdev@free.fr> wrote:

I think we need to be a little careful here—the mere fact that a message returns `Void` doesn't mean the caller shouldn't wait until it's done to continue. For instance:

  listActor.delete(at: index) // Void, so it doesn't wait
  let count = await listActor.getCount() // But we want the count *after* the deletion!

In fact this will just work. Because both messages happen on the actor's internal serial queue, the "get count" message will only happen after the deletion. Therefore the "delete" message can return immediately to the caller (you just need the dispatch call on the queue to be made).

Suppose `delete(at:)` needs to do something asynchronous, like ask a server to do the deletion. Is processing of other messages to the actor suspended until it finishes? (Maybe the answer is "yes"—I don't have experience with proper actors.)

It seems like the answer should be "yes". But then how do you implement something like a cancel() method? I don't know how the actor model solves that problem.

If I understand correctly this queue/thread hopping problem arises because we do not want `await` to block. If `await` would block we would trivially stay in the same thread/on the same queue.
Actually I think that the mental model and the semantics should work as if `await` did block. Anything else will make concurrency with async/await not easier to use than using GCD directly but harder.
To preserve the mental model and semantics the continuation has to be enqueued on the same queue where the code encountering the `await` is currently running.

-Thorsten

···

Am 21.08.2017 um 20:04 schrieb Adam Kemp via swift-evolution <swift-evolution@swift.org>:

On Aug 18, 2017, at 8:38 PM, Chris Lattner <clattner@nondot.org> wrote:

On Aug 18, 2017, at 2:09 PM, Adam Kemp <adam.kemp@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.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

You would use something like the `Future` type mentioned in the proposal:

  func process() {
    let bigDataFuture = Future { await loadWebResource("bigData.txt") }
    print("BigData scheduled to load")
    
    let smallDataFuture = Future { await loadWebResource("smallData.txt") }
    print("SmallData scheduled to load")
    
    let bigDataResource = await bigDataFuture.get()
    let smallDataResource = await smallDataFuture.get()
    // or whatever; you could probably chain off the futures to handle whichever happens first first.
    ...
  }

···

On Aug 21, 2017, at 12:41 PM, Wallacy via swift-evolution <swift-evolution@swift.org> wrote:

Based on these same concerns, how to do this using async/await ?

func process() -> Void) {
    loadWebResource("bigData.txt") { dataResource in
    //....
    }
    printf("BigData Scheduled to load")
    loadWebResource("smallData.txt") { dataResource in
    //....
    }
    printf("SmallData Scheduled to load")

}

--
Brent Royal-Gordon
Architechies

Alternatively, until futures are a thing, you can call `beginAsync` twice to start to async tasks:

func process() -> Void {
  beginAsync {
    let dataResource = await loadWebResource("bigData.txt")
    //....
  }
  
  print("BigData Scheduled to load")
  beginAsync {
    let dataResource = await loadWebResource("smallData.txt")
    //....
  }
}

Futures have a number of advantages. For instance, you can use a nullable Future to keep track of whether the task has been started at all.

FĂ©lix

···

Le 21 août 2017 à 13:32, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

On Aug 21, 2017, at 12:41 PM, Wallacy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Based on these same concerns, how to do this using async/await ?

func process() -> Void) {
    loadWebResource("bigData.txt") { dataResource in
    //....
    }
    printf("BigData Scheduled to load")
    loadWebResource("smallData.txt") { dataResource in
    //....
    }
    printf("SmallData Scheduled to load")

}

You would use something like the `Future` type mentioned in the proposal:

  func process() {
    let bigDataFuture = Future { await loadWebResource("bigData.txt") }
    print("BigData scheduled to load")
    
    let smallDataFuture = Future { await loadWebResource("smallData.txt") }
    print("SmallData scheduled to load")
    
    let bigDataResource = await bigDataFuture.get()
    let smallDataResource = await smallDataFuture.get()
    // or whatever; you could probably chain off the futures to handle whichever happens first first.
    ...
  }

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I found a decent description about async/await here:

Asynchronous programming scenarios - C# | Microsoft Learn

So it’s much more like runloop based callbacks in Foundation that libdispatch. It’s complicated. Even the simplest example is super complicated.

It’s super implicit (like, “I’m going to package up every line of code from await to the end of this function and turn it into a continuation”). That seems to go against one of the primary principles of Swift, which is to make things plain to the reader. I’d be interested to know what the call stack looks like on the line after await.

This is pretty much how it would have to work for Swift as well. The call stack after the await (in C#) would either start at the runloop and go through the futures API (usually Task) or it would start at whatever code satisfied the async request.

It’s true that this can make it more difficult to understand stack traces. In most cases the original call stack is lost. Microsoft has made changes to Visual Studio in order to show kind of an alternative stack trace for tasks to try to make this better. I think they also made things like F10 (step over) and F11 (step out) do the natural thing (i.e., wait for the continuation).

The doc takes away some of the mystery, but leaves major questions, like: await is used to yield control to the parent, but at the bottom of the call stack, presumably you’re going to do something blocking, so how do you call await?

One of the common misconceptions about async/await (which I also had when I first encountered it) is that there must be a blocking thread somewhere. It doesn’t work that way. The “bottom of the call stack” is typically either a run loop or a thread pool with a work queue (really just another kind of run loop). I guess you’re right in the sense that those kinds of run loops do block, but they’re not blocking on any particular piece of work to be done. They’re blocking waiting for ANY more work to be done (either events or items placed in the work queue).

The way that the continuation works is that it is placed onto one of those queues. For the UI thread it’s kind of like doing performSelectorOnMainThread: (the .Net equivalent is usually called BeginInvokeOnMainThread). For a thread pool there’s another API. For GCD this would be like doing a dispatch_async. It’s putting the continuation callback block onto a queue, and that callback will be called when the run loop or the thread pool is able to do so.

···

On Sep 11, 2017, at 1:15 PM, Kenny Leung via swift-evolution <swift-evolution@swift.org> wrote:

Based on these same concerns, how to do this using async/await ?

func process() -> Void) {
    loadWebResource("bigData.txt") { dataResource in
    //....
    }
    printf("BigData Scheduled to load")
    loadWebResource("smallData.txt") { dataResource in
    //....
    }
    printf("SmallData Scheduled to load")

}

You would use something like the `Future` type mentioned in the proposal:

  func process() {
    let bigDataFuture = Future { await loadWebResource("bigData.txt") }
    print("BigData scheduled to load")
    
    let smallDataFuture = Future { await loadWebResource("smallData.txt") }
    print("SmallData scheduled to load")
    
    let bigDataResource = await bigDataFuture.get()
    let smallDataResource = await smallDataFuture.get()
    // or whatever; you could probably chain off the futures to handle whichever happens first first.
    ...
  }

Like others have already proposed I would imagine to be able to write something like this (adding a return type to do something with the data).

func process() async -> (Data, Data) {
    let bigData = async loadWebResource("bigData.txt")
    print("BigData scheduled to load")
    let bigData = async loadWebResource("bigData.txt")
    print("SmallData scheduled to load")
    return await (bigData, smallData)
}

where bigData and smallData have the type `async Data` which has
to be `await`ed upon to get at the `Data`.

-Thorsten

···

Am 21.08.2017 um 22:32 schrieb Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org>:

On Aug 21, 2017, at 12:41 PM, Wallacy via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution