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