[swift-evolution] [Concurrency] async/await + actors
Adam Kemp
adam.kemp at apple.com
Fri Aug 18 14:34:16 CDT 2017
I am very interested in the async/await proposal. I will say up front that I have experience using C#’s async/await (with Cocoa via Xamarin) so I am biased towards what I am familiar with. That said, I think there are some advantages to the way C# does it that the current proposal is missing out on. One of the differences was touched on in Brent’s question here:
> On Aug 17, 2017, at 11:34 PM, Brent Royal-Gordon via swift-evolution <swift-evolution at swift.org> wrote:
>
> ## Dispatching back to the original queue
>
> You correctly identify one of the problems with completion blocks as being that you can't tell which queue the completion will run on, but I don't think you actually discuss a solution to that in the async/await section. Do you think async/await can solve that? How? Does GCD even have the primitives needed? (`dispatch_get_current_queue()` was deprecated long ago and has never been available in Swift.)
>
> Or do you see this as the province of actors? If so, how does that work? Does every piece of code inherently run inside one actor or another? Or does the concurrency system only get you on the right queue if you explicitly use actors? Can arbitrary classes "belong to" an actor, so that e.g. callbacks into a view controller inherently go to the main queue/actor?
>
> (If you *do* need actors to get sensible queue behavior, I'm not the biggest fan of that; we could really use that kind of thing in many, many places that aren't actors.)
I see this as one of the key advantages to the C# async/await model. One of the goals is to reduce the cognitive load of people writing async code such that you can more easily write correct code while writing in a straightforward way. Being on the right synchronization context without having to think too hard about it is a key part of that.
For instance, say you’re handling a button click, and you need to do a network request and then update the UI. In C# (using Xamarin.iOS as an example) you might write some code like this:
private async void HandleButtonClick(object sender, EventArgs e) {
var results = await GetStuffFromNetwork();
UpdateUI(results);
}
This event handler is called on the UI thread, and the UpdateUI call must be done on the UI thread. The way async/await works in C# (by default) is that when your continuation is called it will be on the same synchronization context you started with. That means if you started on the UI thread you will resume on the UI thread. If you started on some thread pool then you will resume on that same thread pool.
If you didn’t have that guarantee then every time you write “await" you would have to think about where that continuation code should run, and you would have to explicitly make that happen. That might mean something like this:
private async void HandleButtonClick(object sender, EventArgs e) {
var results = await GetStuffFromNetworkAsync();
BeginInvokeOnMainThread(() => {
UpdateUI(results);
});
}
That code doesn’t look much different from the non async/await code:
private void HandleButtonClick(object sender, EventArgs e) {
GetStuffFromNetwork(results => {
BeginInvokeOnMainThread(() => {
UpdateUI(results);
});
});
}
Error handling would only complicate it further.
I feel like this is a very important aspect of how async/await simplifies asynchronous programming, and it is especially valuable in application development, which is a very important use case for Swift. Obviously GCD is a bit different than C#’s Task Parallel Library model, but I feel like there must be some way to reconcile these models.
####
Another difference between the C# implementation and this proposal is the lack of futures. While I think it’s fair to be cautious about tying this proposal to any specific futures implementation or design, I feel like the value of tying it to some concept of futures was somewhat overlooked. For instance, in C# you could write a method with this signature:
public Task<int> GetIntAsync();
Consumers could then call that using await:
int i = await GetIntAsync();
Or they could just get the Task and pass it off to someone else:
Task<int> t = GetIntAsync();
return t;
Or they could get the Task and await it, but do something in between:
Task<int> t = GetIntAsync();
// Do something
await t;
That’s more useful when shown like this:
List<Task<int>> calculations = inputs.Select(input => GetIntAsync(input)).ToList();
int[] results = await Task.WhenAll(calculations);
The benefit of connecting the async/await feature to the concept of futures is that you can mix and match this code freely. The current proposal doesn’t seem to allow this. If you wanted to implement that last example in Swift you would need to write GetIntAsync differently than if you were implementing the simple case (just awaiting the one call). With the C# implementation you don’t have to care about whether your caller is going to use await or use the futures API directly. It’s flexible, and that makes it powerful.
The good news is that C#’s implementation is not actually tied to a particular futures implementation. Task/Task<T> is the common implementation, but the language will support using await on any type that has a GetAwaiter method (even an extension method) that returns a type that has certain methods on it (read more here: https://blogs.msdn.microsoft.com/pfxteam/2011/01/13/await-anything/ <https://blogs.msdn.microsoft.com/pfxteam/2011/01/13/await-anything/>). You can use this to implement your own futures API that still interoperates with async/await.
I don’t see a reason that Swift can’t do something similar.
I would really like to see more exploration of these concepts because I feel like they are both very important pieces of what makes async/await work so well in C#.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170818/12547c98/attachment.html>
More information about the swift-evolution
mailing list