dotnet / dotnet/vblang

Thoughts on simplifying Async

Open
#225 0 comments 2 reactions 0 assignees View on GitHub
Discussion
Dominant language
No language data
Stars
328
Forks
71
PR merge metrics
No merged PRs in 30d

Description

Since it's initial release I've wanted to go back and take another pass at simplifying the experience of `Async`/`Await`. I had some ideas of how to make the design simpler while we were designing the feature originally but we struggled to come up with a model that was flexible and consistent. I've heard a lot of stories from customers about where they or developers they know have tripped over themselves and these problems keep coming up again and again:

* `Async Sub` is easily misused.
* `ConfigureAwait` is easily forgotten.
* `Await` is on the left.

The last one is more an annoyance while the first two are in my opinion gaping flaws in an otherwise elegant feature. I'm kicking off this thread as a sort of "thinking out loud" space.

I think the model we have today where there are two kinds of synchronous methods:
* Subs, which don't return a value, and
* Functions, which do

But _three_ kinds of asynchronous methods:
* `Task`-returning async methods, which asynchronously don't return a value,
* `Task(Of T)`-returning async methods, which asynchronously do return a value, and
* void-returning async methods, which asynchronously don't return a value and can't be awaited

Is 50% more kinds of methods than we need. And the only real motivation for having them is method overrides and event handlers whose existing signatures are incompatible with returning a value. But VB already has the ability to handle this through delegate relaxation. In fact, we added a special warning to VB so that if you do try to drop the return value of a `Task`-returning async function through delegate relaxation, we report a warning.

Here's what I want:
* `Async Sub M()` to translate to `Async Function M() As Task`, and
* `Async Function M() As T` to translate to `Async Function M() As Task(Of T)`

We can do it compatibly a couple of ways:
* The magic `Option` statement. I think such a simplification would be worth it.
* A more target heuristic: In a future version of VB `Async Sub` declarations could be awaitable but preserve the existing behavior if they aren't awaited (which they can't be today) or hooked up to an event handler. And `Async Function` declarations which return a type _other than_ `Task` or `Task(Of T)` will be interpreted as return `Task(Of T)` where `T` is their stated return type.

The latter approach has a slight problem in the rare case that you want to asynchronously return a task (e.g. `WaitAny`) you'd still need to use the syntax `Task(Of Task)`. There are _some_ ways we can mitigate that. And it also doesn't help a situation where today someone is passing an `Async Sub` lambda to a method that takes a synchronous delegate. We also report a warning if an asynchronous delegate-taking overload exists but this would still represent a change in behavior, which I find extremely distasteful. That makes more more inclined to deprecation through an `Option` statement.

The problem with all of this is coming up with a model that is consistent. Particularly an IDE experience. Gotta go. Will keep adding thoughts later.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.