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

Champion "Async Main" (C# 7.1) #97

Open
gafter opened this Issue Feb 14, 2017 · 19 comments

Comments

Projects
None yet
@gafter
Member

gafter commented Feb 14, 2017

  • Proposal added: proposals/async-main.md
  • Discussed in LDM
  • Decision in LDM
  • Finalized (done, rejected, inactive)
  • Spec'ed

See also dotnet/roslyn#1695 dotnet/roslyn#7476

@gafter gafter changed the title from Async Main to Champion Async Main Feb 15, 2017

@gafter gafter changed the title from Champion Async Main to Champion "Async Main" Feb 21, 2017

@gafter gafter added this to the 7.1 candidate milestone Feb 21, 2017

@yaakov-h

This comment has been minimized.

Show comment
Hide comment
@yaakov-h

yaakov-h Apr 12, 2017

Contributor

Can we add a link from the above post to the proposal text?

Contributor

yaakov-h commented Apr 12, 2017

Can we add a link from the above post to the proposal text?

@stephentoub

This comment has been minimized.

Show comment
Hide comment
@stephentoub

stephentoub Apr 12, 2017

Member

Can we add a link from the above post to the proposal text?

Done

Member

stephentoub commented Apr 12, 2017

Can we add a link from the above post to the proposal text?

Done

@jcouv

This comment has been minimized.

Show comment
Hide comment

@jcouv jcouv changed the title from Champion "Async Main" to Champion "Async Main" (C# 7.1) Jun 5, 2017

@dbmercer

This comment has been minimized.

Show comment
Hide comment
@dbmercer

dbmercer Jun 6, 2017

Meh. I don't really see much gain from this purely syntactic sugar change. The lack of an async Main may have been an oversight, but this solution doesn't resolve the oversight. Sure, now you can have an async Main, but you gain nothing over what you could have previously done with one line of code. An alternative async Main implementation could have passed in a cancellation token, and had the runtime host cancel the token if, say, a kill signal was received.

dbmercer commented Jun 6, 2017

Meh. I don't really see much gain from this purely syntactic sugar change. The lack of an async Main may have been an oversight, but this solution doesn't resolve the oversight. Sure, now you can have an async Main, but you gain nothing over what you could have previously done with one line of code. An alternative async Main implementation could have passed in a cancellation token, and had the runtime host cancel the token if, say, a kill signal was received.

@nbarbettini

This comment has been minimized.

Show comment
Hide comment
@nbarbettini

nbarbettini Jun 6, 2017

Sure, now you can have an async Main, but you gain nothing over what you could have previously done with one line of code.

I think this is still valuable, because it reduces confusion. Having to hit StackOverflow to learn that I need to add .GetAwaiter().GetResult() is nonobvious for a newbie.

An alternative async Main implementation could have passed in a cancellation token, and had the runtime host cancel the token if, say, a kill signal was received.

This would be great. I'd love to see built-in passing of a cancellation token that was linked to the kill signal. 👍

nbarbettini commented Jun 6, 2017

Sure, now you can have an async Main, but you gain nothing over what you could have previously done with one line of code.

I think this is still valuable, because it reduces confusion. Having to hit StackOverflow to learn that I need to add .GetAwaiter().GetResult() is nonobvious for a newbie.

An alternative async Main implementation could have passed in a cancellation token, and had the runtime host cancel the token if, say, a kill signal was received.

This would be great. I'd love to see built-in passing of a cancellation token that was linked to the kill signal. 👍

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Jun 6, 2017

Contributor

@dbmercer

I kind of agree. This is low hanging fruit, but also something very easily accomplished by a library. Where a library would have a significant advantage would be in the flexibility of handling different scenarios, such as wiring up a CancellationToken to a kill signal as you've described, which is unlikely for the language feature to do.

static class Program {
    static int Main(string[] args) {
        return Console.Async(async cancellationToken => {
            await DoStuffAsync(cancellationToken);
            return 0;
        });
    } 
}
Contributor

HaloFour commented Jun 6, 2017

@dbmercer

I kind of agree. This is low hanging fruit, but also something very easily accomplished by a library. Where a library would have a significant advantage would be in the flexibility of handling different scenarios, such as wiring up a CancellationToken to a kill signal as you've described, which is unlikely for the language feature to do.

static class Program {
    static int Main(string[] args) {
        return Console.Async(async cancellationToken => {
            await DoStuffAsync(cancellationToken);
            return 0;
        });
    } 
}
@stephentoub

This comment has been minimized.

Show comment
Hide comment
@stephentoub

stephentoub Jun 6, 2017

Member

There are a myriad of possible things you might want to do in your "entrypoint". You might want to respond to signals. You might want a serialized synchronization context. You might want ... etc. This feature is not meant to cover all possible scenarios, which @HaloFour highlights can be done by library. Rather, this simple feature is meant to eliminate the really annoying boilerplate that everyone has to find and copy or write to use Task-returning methods in their code:

static void Main(string[] args) => MainAsync(args).GetAwaiter().GetResult();
static async Task Main(string[] args)
{
}

becoming:

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

such that a dev only needs to change void to async Task. It's the simple, most policy-free thing that can be incorporated into the language (it also doesn't preclude additional overloads being supported in the future if it becomes important to do so). For anything with more policy, such as the (valuable but more complicated) semantics mentioned, libraries are the way to go.

Member

stephentoub commented Jun 6, 2017

There are a myriad of possible things you might want to do in your "entrypoint". You might want to respond to signals. You might want a serialized synchronization context. You might want ... etc. This feature is not meant to cover all possible scenarios, which @HaloFour highlights can be done by library. Rather, this simple feature is meant to eliminate the really annoying boilerplate that everyone has to find and copy or write to use Task-returning methods in their code:

static void Main(string[] args) => MainAsync(args).GetAwaiter().GetResult();
static async Task Main(string[] args)
{
}

becoming:

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

such that a dev only needs to change void to async Task. It's the simple, most policy-free thing that can be incorporated into the language (it also doesn't preclude additional overloads being supported in the future if it becomes important to do so). For anything with more policy, such as the (valuable but more complicated) semantics mentioned, libraries are the way to go.

@mblataric

This comment has been minimized.

Show comment
Hide comment
@mblataric

mblataric Aug 16, 2017

Hi,

I am trying new async Main in VS 2017 15.3 in one of my projects, but I get following exception:

System.Threading.ThreadStateException: 'Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.'
Main method does have STAThread attribute.

Are there restrictions with using async Main?

Thanks,
Mario

mblataric commented Aug 16, 2017

Hi,

I am trying new async Main in VS 2017 15.3 in one of my projects, but I get following exception:

System.Threading.ThreadStateException: 'Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.'
Main method does have STAThread attribute.

Are there restrictions with using async Main?

Thanks,
Mario

@gulshan

This comment has been minimized.

Show comment
Hide comment
@gulshan

gulshan Aug 16, 2017

Try adding <LangVersion>Latest</LangVersion> or <LangVersion>7.1</LangVersion> to csproj file.

gulshan commented Aug 16, 2017

Try adding <LangVersion>Latest</LangVersion> or <LangVersion>7.1</LangVersion> to csproj file.

@mblataric

This comment has been minimized.

Show comment
Hide comment
@mblataric

mblataric Aug 16, 2017

Hi,

Yes I changed language to 7.1 (it says there is no valid Main method if you don't).
My app is WinForms app and it seems some of the visual components code calls OLE methods.
Is that a fix restriction with async Main or is there a way to make it work?

mblataric commented Aug 16, 2017

Hi,

Yes I changed language to 7.1 (it says there is no valid Main method if you don't).
My app is WinForms app and it seems some of the visual components code calls OLE methods.
Is that a fix restriction with async Main or is there a way to make it work?

@jcouv

This comment has been minimized.

Show comment
Hide comment
@jcouv

jcouv Aug 16, 2017

Member

FYI @TyOverby For question above

Member

jcouv commented Aug 16, 2017

FYI @TyOverby For question above

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Aug 16, 2017

Contributor

I didn't think of that. Either STAThread will only affect code prior to the first await, or you'll have to install a synchronization context. It's easy,see nunit/nunit#1200 (comment).

Edit: Actually, since STAThread does nothing on async Main you'll have to just not use async Main.

Should that be automatically installed behind the scenes if you use the STAThread attribute? It would be super handy for SingleThreadedSynchronizationContext to be in the BCL too.

Contributor

jnm2 commented Aug 16, 2017

I didn't think of that. Either STAThread will only affect code prior to the first await, or you'll have to install a synchronization context. It's easy,see nunit/nunit#1200 (comment).

Edit: Actually, since STAThread does nothing on async Main you'll have to just not use async Main.

Should that be automatically installed behind the scenes if you use the STAThread attribute? It would be super handy for SingleThreadedSynchronizationContext to be in the BCL too.

@TyOverby

This comment has been minimized.

Show comment
Hide comment
@TyOverby

TyOverby Aug 16, 2017

@mblataric: Any attributes that you put on the async Main method will remain on that method, but this method isn't considered to be "the Main method" by the runtime.

Unfortunately this is a leaky abstraction that offers hints into how the feature was implemented. Under the hood, we create a new method called $main which contains the boilerplate Main().GetAwaiter().GetResult(). Because there is a new method, attributes are not carried over.

The decision to not move attributes was a tough one, but in the end we decided to for a few reasons

  • Moving the attributes was not considered because the original Main method is still around and perfectly usable.
  • Copying the attributes was considered but rejected because it would not be obvious to the user that there would be another method with the attributes of the original.

That leaves us where we are today: Attributes are not copied or moved to the hidden "real" Main method. @gafter: do you have any suggestions on where these details could be documented so that more people aren't left trying to interpret runtime errors?

TyOverby commented Aug 16, 2017

@mblataric: Any attributes that you put on the async Main method will remain on that method, but this method isn't considered to be "the Main method" by the runtime.

Unfortunately this is a leaky abstraction that offers hints into how the feature was implemented. Under the hood, we create a new method called $main which contains the boilerplate Main().GetAwaiter().GetResult(). Because there is a new method, attributes are not carried over.

The decision to not move attributes was a tough one, but in the end we decided to for a few reasons

  • Moving the attributes was not considered because the original Main method is still around and perfectly usable.
  • Copying the attributes was considered but rejected because it would not be obvious to the user that there would be another method with the attributes of the original.

That leaves us where we are today: Attributes are not copied or moved to the hidden "real" Main method. @gafter: do you have any suggestions on where these details could be documented so that more people aren't left trying to interpret runtime errors?

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Aug 16, 2017

Contributor

Could you special-case the STAThread attribute to be copied to $main and also automatically install a SingleThreadedSynchronizationContext?

Contributor

jnm2 commented Aug 16, 2017

Could you special-case the STAThread attribute to be copied to $main and also automatically install a SingleThreadedSynchronizationContext?

@TyOverby

This comment has been minimized.

Show comment
Hide comment
@TyOverby

TyOverby Aug 16, 2017

@jnm2: The compiler doesn't know what STAThread or SingleThreadedSynchronizationContext is. We try to keep the coupling between language and framework as minimal as possible.

TyOverby commented Aug 16, 2017

@jnm2: The compiler doesn't know what STAThread or SingleThreadedSynchronizationContext is. We try to keep the coupling between language and framework as minimal as possible.

@tannergooding

This comment has been minimized.

Show comment
Hide comment
@tannergooding

tannergooding Aug 16, 2017

Member

@TyOverby, I agree. However, it is likely worth providing a way to propagate attributes placed on the async Main method to the actual Main method, as this is the only way to enforce certain runtime behaviors (as happens with STAThread and MTAThread).

Otherwise, async Main is essentially unusable from any kind of UI based application.

Member

tannergooding commented Aug 16, 2017

@TyOverby, I agree. However, it is likely worth providing a way to propagate attributes placed on the async Main method to the actual Main method, as this is the only way to enforce certain runtime behaviors (as happens with STAThread and MTAThread).

Otherwise, async Main is essentially unusable from any kind of UI based application.

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Aug 16, 2017

Contributor

If Main is async, it's because you're going to await which will break the STAThread unless you install a synchronization context, which ideally would block and pump rather than starting a new STA thread. If it's going to block, a custom synchronization context API could still give the illusion that you await the pump and the continuation actually executes inside the pump, but that is weird and it's still boilerplate. May as well stick with synchronous Main for UI code if you aren't going to automatically install that sync context.

Contributor

jnm2 commented Aug 16, 2017

If Main is async, it's because you're going to await which will break the STAThread unless you install a synchronization context, which ideally would block and pump rather than starting a new STA thread. If it's going to block, a custom synchronization context API could still give the illusion that you await the pump and the continuation actually executes inside the pump, but that is weird and it's still boilerplate. May as well stick with synchronous Main for UI code if you aren't going to automatically install that sync context.

@tannergooding

This comment has been minimized.

Show comment
Hide comment
@tannergooding
Member

tannergooding commented Sep 14, 2017

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Sep 15, 2017

Member

That leaves us where we are today: Attributes are not copied or moved to the hidden "real" Main method. @gafter: do you have any suggestions on where these details could be documented so that more people aren't left trying to interpret runtime errors?

I think we need to take this to the LDM. I suspect we will want to copy certain attributes such as this one. I will bring the issue up.

Member

gafter commented Sep 15, 2017

That leaves us where we are today: Attributes are not copied or moved to the hidden "real" Main method. @gafter: do you have any suggestions on where these details could be documented so that more people aren't left trying to interpret runtime errors?

I think we need to take this to the LDM. I suspect we will want to copy certain attributes such as this one. I will bring the issue up.

@jcouv jcouv self-assigned this Jul 7, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment