From 3d15be368b0acd7e74a990d2e43a19f657635eb9 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 14 Feb 2017 18:00:29 -0800 Subject: [PATCH] Add a proposal for async Main --- proposals/async-main.md | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 proposals/async-main.md diff --git a/proposals/async-main.md b/proposals/async-main.md new file mode 100644 index 0000000..95dd090 --- /dev/null +++ b/proposals/async-main.md @@ -0,0 +1,92 @@ +# Async Main + +* [x] Proposed +* [ ] Prototype +* [ ] Implementation +* [ ] Specification + +## Summary +[summary]: #summary + +Allow `await` to be used in an application's Main / entrypoint method by allowing the entrypoint to return `Task` / `Task` and be marked `async`. + +## Motivation +[motivation]: #motivation + +It is very common when learning C#, when writing console-based utilities, and when writing small test apps to want +to call and `await` `async` methods from Main. Today we add a level of complexity here by forcing such `await`'ing to be +done in a separate async method, which causes developers to need to write boilerplate like the following just to get +started: +```C# +public static void Main() +{ + MainAsync().GetAwaiter().GetResult(); +} + +private static async Task MainAsync() +{ + ... // Main body here +} +``` +We can remove the need for this boilerplate and make it easier to get started simply by allowing Main itself to be +`async` such that `await`s can be used in it. + +## Detailed design +[design]: #detailed-design + +The following signatures are currently allowed entrypoints: +```C# +static void Main() +static void Main(string[]) +static int Main() +static int Main(string[]) +``` + +We extend the list of allowed entrypoints to include: +``` +static Task Main() +static Task Main() +static Task Main(string[]) +static Task Main(string[]) +``` +To avoid compatibility risks, these new signatures will only be considered as valid entrypoints if no overloads of the previous set are present. +The language / compiler will not require that the entrypoint be marked as `async`, though we expect the vast majority of uses will be marked as such. + +When one of these is identified as the entrypoint, the compiler will synthesize an actual entrypoint method that calls one of these coded methods: +- ```static Task Main()``` will result in the compiler emitting the equivalent of ```private static void $GeneratedMain() => Main().GetAwaiter().GetResult();``` +- ```static Task Main(string[])``` will result in the compiler emitting the equivalent of ```private static void $GeneratedMain(string[] args) => Main(args).GetAwaiter().GetResult();``` +- ```static Task Main()``` will result in the compiler emitting the equivalent of ```private static int $GeneratedMain() => Main().GetAwaiter().GetResult();``` +- ```static Task Main(string[])``` will result in the compiler emitting the equivalent of ```private static int $GeneratedMain(string[] args) => Main(args).GetAwaiter().GetResult();``` + +Example usage: +```C# +using System; +using System.Net.Http; + +class Test +{ + static async Task Main(string[] args) => + Console.WriteLine(await new HttpClient().GetStringAsync(args[0])); +} +``` + +## Drawbacks +[drawbacks]: #drawbacks + +The main drawback is simply the additional complexity of supporting additional entrypoint signatures. + +## Alternatives +[alternatives]: #alternatives + +Other variants considered: +- Allowing `async void`. This is problematic as to track the operation's completion we would need to do complicated work with SynchronizationContext (or else change the return type to be Task, but that would break code expecting this specific signature). +- Using "MainAsync" instead of "Main" as the name. While the async suffix is recommended for Task-returning methods, that's primarily about library functionality, which Main is not, and supporting additional entrypoint names beyond "Main" is not worth it. + +## Unresolved questions +[unresolved]: #unresolved-questions + +n/a + +## Design meetings + +n/a