Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Add Async support for Main() / Console Apps #1695

Closed
thedanfernandez opened this issue Mar 31, 2015 · 21 comments
Closed

Proposal: Add Async support for Main() / Console Apps #1695

thedanfernandez opened this issue Mar 31, 2015 · 21 comments

Comments

@thedanfernandez
Copy link

Background

The console project template has shipped since Visual Studio 2002 and it continues to see high usage by developers who are either building command line interfaces, background application services (esp for Cloud), testing code, or just learning a new API.

The need for simple apps has been expanded as new project types like WebJobs, Cloud Service Worker Roles, and ASP.NET 5 Console applications have been added that provide a template for running background application services or cross-platform console apps respectively. The template below shows what's currently in Visual Studio 2013:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

Problem

The problem arises in that a Main method cannot be marked as async. Given the rise of async/await in all Microsoft or 3rd party APIs, many customers have to write boilerplate code that's designed around the lack of support for async support in Main(), where they build another method and wait() for it.

In short:

  • Many developers are still using Console apps and the need continues to grow
  • Having async support for Main() is as fundamental a requirement as LINQ for modern app developers

Proposed Solution

The code I would expect would be that I could add the async keyword with a Task for the return type as shown below (friends don't let friends async void) and I can await() to my heart's content directly in a Main() method.

    class Program
    {
        static async Task Main(string[] args)
        {
            await Task.Delay(1000);
            var client = new System.Net.Http.HttpClient();
            Console.WriteLine(await client.GetStringAsync("http://www.microsoft.com")); 
        }
    }

There still a number of things to iron out from an implementation perspective. In an email thread with Lucian Wischik and Mads Torgerson, they both raised some considerations to think about, including:

  • Should this be done behind the scenes using Task.Run and synchronously awaited? What are the drawbacks?
  • Should this be single-threaded or multi-threaded?
  • Should it pump COM messages?
@HaloFour
Copy link

Sounds reasonable. I'd think that the compiler could automatically handle this by emitting a synchronous entry point method which would then invoke the asynchronous Main method and wait on the returned Task.

static class Program
{
    [STAThread]
    // can return either Task or Task<int>, and can optionally accept string[]
    static async Task<int> Main(string[] args)
    {
        var client = new System.Net.Http.HttpClient();
        Console.WriteLine(await client.GetStringAsync("http://www.microsoft.com"));
        return 0;
    }
}

would be converted into:

static class Program
{
    [STAThread]
    static async Task<int> Main(string[] args)
    {
        var client = new System.Net.Http.HttpClient();
        Console.WriteLine(await client.GetStringAsync("http://www.microsoft.com"));
        return 0;
    }

    [STAThread] // automatically copied by the compiler, default is MTAThread
    // .entrypoint
    static int <Main>_EntryPoint(string[] args) // synthetic method name
    {
        Task<int> task = Main(args);
        return task.Result;
    }
}

This is of course greatly simplifying the problem.

@davidfowl
Copy link
Member

This would be great. We support this in DNX console applications by just waiting on the entrypoint that returns a task within the runtime code itself.

Related to #1081

@artelk
Copy link

artelk commented Aug 12, 2015

That could be a syntactic sugar for something like this:

static int Main(string[] args)
{
    Func<Task<int>> main =
        async () =>
                {
                    await Task.Delay(5000);
                    Console.WriteLine("Done...");
                    return 0;
                };
    return main().Result;
}

@tmat
Copy link
Member

tmat commented Aug 12, 2015

BTW, we are going to do this for scripts: #4495

@vladd
Copy link

vladd commented Aug 12, 2015

It should perhaps make sense to provide (at least optionally) a task scheduler which runs a kind of message loop, so that the execution flow can return to the initial (main) thread after await. (The default task scheduler returns to the thread pool AFAIK.) I didn't find a ready one available (only the ones from WPF and WinForms are available, but they seem to need the message loop itself and the corresponding boilerplate).

@whoisj
Copy link

whoisj commented Aug 20, 2015

👍 because I'm stick of typing this at the start of every project:

static int Main(string[] args)
{
    return Task.Run(async () =>
        {
            // do something...
        }).Result;
}

@paulomorgado
Copy link

You can always write a new project template.

@stephentoub
Copy link
Member

I think we should do this.

For Task-returning entrypoints, the compiler would generate a void-returning entrypoint that invokes the Task-returning method and waits for it synchronously, e.g. for

static async Task Main(string[] args) {}

the compiler generates the equivalent of:

static void $EntrypointMain(string[] args) { Main(args).GetAwaiter().GetResult(); }
static async Task Main(string[] args) {}

For Task<int> returning entrypoints, the compiler would generate an int-returning entrypoint that invokes the Task<int>-returning method, waits for it synchronously, and returns its result, e.g. for

static async Task<int> Main(string[] args) {}

the compiler generates the equivalent of:

static int $EntrypointMain(string[] args) { return Main(args).GetAwaiter().GetResult(); }
static async Task<int> Main() {}

And optionally for void-returning entrypoints, the compiler would behave as if it returned a Task instead of void, e.g. for

static async void Main(string[] args) {}

the compiler would generate the equivalent of:

static void $EntrypointMain(string[] args) { Main(args).GetAwaiter().GetResult(); }
static async Task Main(string[] args) {}

(One minor oddity with the void returning is that the return type will be different from what’s typed if reflection is used to access the method.)

This keeps policy to an absolute minimum and doesn’t try to enforce any specific threading model, hence not doing anything special with regards to SynchronizationContext or TaskScheduler. For anything more complicated, a developer can write their own entrypoint that employs the logic they desire.

@whoisj
Copy link

whoisj commented Oct 8, 2015

👍 @stephentoub

@MgSam
Copy link

MgSam commented Oct 8, 2015

@stephentoub This proposal looks good except for the re-writing of static async void Main(string[] args). If someone is writing that there's a good chance they don't understand how Task works in the first place, and silently re-writing their method to make it do the right thing seems a step too far.

I think it would be better to just disallow (or ignore) this method signature and require an async Main to return a Task. You can have a diagnostic and quick fix to handle the case that the user writes the return type as void.

@stephentoub
Copy link
Member

@MgSam, yeah, I was conflicted about void-returning, which is why I wrote "And optionally...". On the one hand, the language supports async void methods, and part of the point of this feature would be to minimize the friction in starting to use async. Plus, since it's the entrypoint, and since async void methods do integrate with the SynchronizationContext to let it track when the operation has completed, it seems somewhat reasonable for the compiler to implement that tracking in a different way, by changing the return type. However, that's a bit disingenuous, as if it was actually using a SynchronizationContext to do the tracking, it'd be tracking all async void methods (and anything else calling SynchronizationContext.OperationStarted/Ended), not just this one, and we don't want that behavior. Further, I realized after posting this that changing the return type would be a bad thing, not just for reflection, but for anyone actually trying to invoke the method manually. So, in the end I also think it'd be better not to allow async void on entry points.

@RichiCoder1
Copy link

👍 for simply banning async void entry points. Seems like an big footgun either way.

@paulomorgado
Copy link

@stephentoub

And optionally for void -returning entrypoints, the compiler would behave as if it returned a Task instead of void, e.g. for

static async void Main(string[] args) { … }

the compiler would generate the equivalent of:

static void $EntrypointMain(string[] args) { Main(args).GetAwaiter().GetResult(); }
static async Task Main(string[] args) { … }

This wasn't done for unit test methods, why should it be done here?

And what about if there's already a Task Main(string[] args) method?

Is there any other feature in C# where the compiler changes the source element like this? Should there be?

Would that work by taking the /main: compiler switch?

@stephentoub
Copy link
Member

@paulomorgado, did you see my follow-up comments?

This wasn't done for unit test methods, why should it be done here?

Not sure what you mean... several unit testing frameworks support async void tests.

Would that work by taking the /main: compiler switch?

My suggestion is that async Task / async Task<int> be supported on the entrypoint method, whatever it be named.

@paulomorgado
Copy link

@stephentoub, I saw it now. I can't explain how and why my comment only got posted today, but I wrote it yesterday.

@davidfowl
Copy link
Member

Lets's make this happen! What's next?

@nbarbettini
Copy link
Member

👍 for the simplicity of @stephentoub's solution. MainAsync().GetAwaiter().GetResult(); is how I've been side-stepping this in my own code.

@jaredpar jaredpar modified the milestones: 1.2, 1.3 May 4, 2016
@codecore
Copy link

I know nothing. I see that the proposed solutions appear to begin and end at the process boundary. Does it make any sense to ask the runtime to consume a task? The idea is that our programs are not just simple synchronous executables in a sea of others, but rather, a potential set of async components that will # be strung together in some future shell scripting environment. In the age of async, I think we can demand that the runtime step-up. Again, I claim ignorance.

@muratg
Copy link

muratg commented May 16, 2016

👀

@gafter gafter modified the milestones: 2.1, 1.3 Jul 17, 2016
@gafter gafter removed the Retriage label Jul 17, 2016
@ashmind
Copy link
Contributor

ashmind commented Sep 22, 2016

Question: Would there be a problem with updating compiler Main method detection to detect methods named MainAsync as well, as a lower priority than Main?

Not being able to follow normal naming convention here would make my code look sloppy to me.

And since "Hello World" apps are often Console apps, MainAsync would teach proper naming to people who are just starting with C#.

@gafter
Copy link
Member

gafter commented Apr 24, 2017

This has been moved to dotnet/csharplang#97

@gafter gafter closed this as completed Apr 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests