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@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#.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to