ChatGPT解决这个技术问题 Extra ChatGPT

Can't specify the 'async' modifier on the 'Main' method of a console app

I am new to asynchronous programming with the async modifier. I am trying to figure out how to make sure that my Main method of a console application actually runs asynchronously.

class Program
{
    static void Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = bs.GetList();
    }
}

public class Bootstrapper {

    public async Task<List<TvChannel>> GetList()
    {
        GetPrograms pro = new GetPrograms();

        return await pro.DownloadTvChannels();
    }
}

I know this is not running asynchronously from "the top." Since it is not possible to specify the async modifier on the Main method, how can I run code within main asynchronously?

This is no longer the case in C#7.1. Main methods can be async
Here's the C#7.1 blog post announcement. See the section titled Async Main.

L
Liam

As you discovered, in VS11 the compiler will disallow an async Main method. This was allowed (but never recommended) in VS2010 with the Async CTP.

Update, 2017-11-30: As of Visual Studio 2017 Update 3 (15.3), the language now supports an async Main - as long as it returns Task or Task<T>. So you can now do this:

class Program
{
    static async Task Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

The semantics appear to be the same as the GetAwaiter().GetResult() style of blocking the main thread. However, there's no language spec for C# 7.1 yet, so this is only an assumption.

I have recent blog posts about async/await and asynchronous console programs in particular. Here's some background info from the intro post:

If "await" sees that the awaitable has not completed, then it acts asynchronously. It tells the awaitable to run the remainder of the method when it completes, and then returns from the async method. Await will also capture the current context when it passes the remainder of the method to the awaitable. Later on, when the awaitable completes, it will execute the remainder of the async method (within the captured context).

Here's why this is a problem in Console programs with an async Main:

Remember from our intro post that an async method will return to its caller before it is complete. This works perfectly in UI applications (the method just returns to the UI event loop) and ASP.NET applications (the method returns off the thread but keeps the request alive). It doesn't work out so well for Console programs: Main returns to the OS - so your program exits.

One solution is to provide your own context - a "main loop" for your console program that is async-compatible.

If you have a machine with the Async CTP, you can use GeneralThreadAffineContext from My Documents\Microsoft Visual Studio Async CTP\Samples(C# Testing) Unit Testing\AsyncTestUtilities. Alternatively, you can use AsyncContext from my Nito.AsyncEx NuGet package.

Here's an example using AsyncContext; GeneralThreadAffineContext has almost identical usage:

using Nito.AsyncEx;
class Program
{
    static void Main(string[] args)
    {
        AsyncContext.Run(() => MainAsync(args));
    }

    static async void MainAsync(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

Alternatively, you can just block the main Console thread until your asynchronous work has completed:

class Program
{
    static void Main(string[] args)
    {
        MainAsync(args).GetAwaiter().GetResult();
    }

    static async Task MainAsync(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

Note the use of GetAwaiter().GetResult(); this avoids the AggregateException wrapping that happens if you use Wait() or Result.


You can use a simple Wait or Result, and there's nothing wrong with that. But be aware that there are two important differences: 1) all async continuations run on the thread pool rather than the main thread, and 2) any exceptions are wrapped in an AggregateException.
Was having a real problem figuring this out until this (and your blog post). This is by far the easiest method to solve this problem, and you can install the package in the nuget console with just a "install-package Nito.Asyncex" and you are done.
My problem with these solutions is that the debugger (Visual Studio 2017 15.3.1) breaks in the Main method, not on the location where the Exception is thrown, and the Call Stack window is empty.
C# 7.1 has a async main now, might be worth adding to your great answer, @StephenCleary github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/…
If your using the C# 7.1 version in VS 2017 I needed to ensure the project was configured to use the latest version of the language by adding <LangVersion>latest</LangVersion> into the csproj file, as shown here.
C
Chris Moschini

You can solve this with this simple construct:

class Program
{
    static void Main(string[] args)
    {
        Task.Run(async () =>
        {
            // Do any async anything you need here without worry
        }).GetAwaiter().GetResult();
    }
}

That will put everything you do out on the ThreadPool where you'd want it (so other Tasks you start/await don't attempt to rejoin a Thread they shouldn't), and wait until everything's done before closing the Console app. No need for special loops or outside libs.

Edit: Incorporate Andrew's solution for uncaught Exceptions.


This approach is very obvious but tends to wrap exceptions so I'm looking now for a better way.
@abatishchev You should be using try/catch in your code, at least inside the Task.Run if not more granularly, not letting exceptions float up to the Task. You'll avoid the wrap up problem by putting try/catch around things that can fail.
If you replace Wait() with GetAwaiter().GetResult() you'll avoid the AggregateException wrapper when things throw.
This is how async main is being introduced in C# 7.1, as of this writing.
@user9993 According to this proposal, that's not exactly true.
p
p.campbell

You can do this without needing external libraries also by doing the following:

class Program
{
    static void Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var getListTask = bs.GetList(); // returns the Task<List<TvChannel>>

        Task.WaitAll(getListTask); // block while the task completes

        var list = getListTask.Result;
    }
}

Bear in mind that getListTask.Result is also a blocking call and so the above code could be written without Task.WaitAll(getListTask).
Also, if GetList throws you will have to catch an AggregateException and interrogate its exceptions to determine the actual exception thrown. You can, however, call GetAwaiter() to get the TaskAwaiter for the Task, and call GetResult() on that, ie var list = getListTask.GetAwaiter().GetResult();. When getting the result from the TaskAwaiter (also a blocking call) any exceptions thrown will not be wrapped in an AggregateException.
.GetAwaiter().GetResult was the answer I needed. That works perfectly for what I was attempting to do. I will probably use this in other places as well.
M
Mahmoud Al-Qudsi

In C# 7.1 you will be able to do a proper async Main. The appropriate signatures for Main method has been extended to:

public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);
public static Task<int> Main(string[] args);

For e.g. you could be doing:

static async Task Main(string[] args)
{
    Bootstrapper bs = new Bootstrapper();
    var list = await bs.GetList();
}

At compile time, the async entry point method will be translated to call GetAwaitor().GetResult().

Details: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main

EDIT:

To enable C# 7.1 language features, you need to right-click on the project and click "Properties" then go to the "Build" tab. There, click the advanced button at the bottom:

https://i.stack.imgur.com/bxXL4.png

From the language version drop-down menu, select "7.1" (or any higher value):

https://i.stack.imgur.com/7R7zk.png

The default is "latest major version" which would evaluate (at the time of this writing) to C# 7.0, which does not support async main in console apps.


FWIW this is available in Visual Studio 15.3 and up, which is currently available as a beta/preview release from here: visualstudio.com/vs/preview
Wait a minute... I'm running a fully updated install and my latest option is 7.1... how'd you get 7.2 already in May?
May answer was mine. October edit was by someone else by the time I think 7.2 (preview?) might have got released.
Heads up - check that it is on all configs, not just debug when you do this!
@user230910 thanks. One of the weirdest choices by c# team.
C
Cory Nelson

I'll add an important feature that all of the other answers have overlooked: cancellation.

One of the big things in TPL is cancellation support, and console apps have a method of cancellation built in (CTRL+C). It's very simple to bind them together. This is how I structure all of my async console apps:

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();
    
    System.Console.CancelKeyPress += (s, e) =>
    {
        e.Cancel = true;
        cts.Cancel();
    };

    MainAsync(args, cts.Token).GetAwaiter.GetResult();
}

static async Task MainAsync(string[] args, CancellationToken token)
{
    ...
}

Should the cancellation token be passed to the Wait() as well?
No, because you want the async code a be able to gracefully handle the cancellation. If you pass it to the Wait(), it won't wait for the async code to finish -- it'll stop waiting and end the process immediately.
Are you sure about that? I've just tried it out, and it seems like the cancellation request is being processed at the deepest level, even when the Wait() method is passed the same token. What I'm trying to say is, that it doesn't seem to make any difference.
I'm sure. You want to cancel the op itself, not the wait for the op to finish. Unless you don't care about cleanup code finishing or the result of it.
Yeah, I think I get it, it just didn't seem to make any difference in my code. Another thing that threw me off course was a polite ReSharper hint about the wait method supporting cancellation ;) You might want to include a try catch in the example, as it will throw an OperationCancelledException, which I couldn't figure out at first
M
M.Hassan

C# 7.1 (using vs 2017 update 3) introduces async main

You can write:

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

For more details C# 7 Series, Part 2: Async Main

Update:

You may get a compilation error:

Program does not contain a static 'Main' method suitable for an entry point

This error is due to that vs2017.3 is configured by default as c#7.0 not c#7.1.

You should explicitly modify the setting of your project to set c#7.1 features.

You can set c#7.1 by two methods:

Method 1: Using the project settings window:

Open the settings of your project

Select the Build tab

Click the Advanced button

Select the version you want As shown in the following figure:

https://i.stack.imgur.com/dKR3U.png

Method2: Modify PropertyGroup of .csproj manually

Add this property:

    <LangVersion>7.1</LangVersion>

example:

    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
        <PlatformTarget>AnyCPU</PlatformTarget>
        <DebugSymbols>true</DebugSymbols>
        <DebugType>full</DebugType>
        <Optimize>false</Optimize>
        <OutputPath>bin\Debug\</OutputPath>
        <DefineConstants>DEBUG;TRACE</DefineConstants>
        <ErrorReport>prompt</ErrorReport>
        <WarningLevel>4</WarningLevel>
        <Prefer32Bit>false</Prefer32Bit>
        <LangVersion>7.1</LangVersion>
    </PropertyGroup>    

Ş
Şafak Gür

If you're using C# 7.1 or later, go with the nawfal's answer and just change the return type of your Main method to Task or Task<int>. If you are not:

Have an async Task MainAsync like Johan said.

Call its .GetAwaiter().GetResult() to catch the underlying exception like do0g said.

Support cancellation like Cory said.

A second CTRL+C should terminate the process immediately. (Thanks binki!)

Handle OperationCancelledException - return an appropriate error code.

The final code looks like:

private static int Main(string[] args)
{
    var cts = new CancellationTokenSource();
    Console.CancelKeyPress += (s, e) =>
    {
        e.Cancel = !cts.IsCancellationRequested;
        cts.Cancel();
    };

    try
    {
        return MainAsync(args, cts.Token).GetAwaiter().GetResult();
    }
    catch (OperationCanceledException)
    {
        return 1223; // Cancelled.
    }
}

private static async Task<int> MainAsync(string[] args, CancellationToken cancellationToken)
{
    // Your code...

    return await Task.FromResult(0); // Success.
}

Many nice programs will cancel the CancelKeyPress only the first time so that if you press ^C once you get a graceful shutdown but if you’re impatient a second ^C terminates ungracefully. With this solution, you’d need to manually kill the program if it fails to honor the CancellationToken because e.Cancel = true is unconditional.
J
Johan Falk

Haven't needed this much yet, but when I've used console application for Quick tests and required async I've just solved it like this:

class Program
{
    static void Main(string[] args)
    {
        MainAsync(args).Wait();
    }

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}

This example will work wrong in case when you have to schedule task to current context and then wait it (for example, you can forget to add ConfigureAwait(false), so return method will be scheduled into the main thread, which is in Wait function). Because the current thread is in wait state, you will receive a deadlock.
Not true, @ManushinIgor. At least in this trivial example, there is no SynchronizationContext associated with the main thread. So it won't deadlock because even without ConfigureAwait(false), all continuations will execute on the threadpool.
u
user3408030

For asynchronously calling task from Main, use

Task.Run() for .NET 4.5 Task.Factory.StartNew() for .NET 4.0 (May require Microsoft.Bcl.Async library for async and await keywords)

Details: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx


F
Fredrik Ljung

In Main try changing the call to GetList to:

Task.Run(() => bs.GetList());

J
Jon Skeet

When the C# 5 CTP was introduced, you certainly could mark Main with async... although it was generally not a good idea to do so. I believe this was changed by the release of VS 2013 to become an error.

Unless you've started any other foreground threads, your program will exit when Main completes, even if it's started some background work.

What are you really trying to do? Note that your GetList() method really doesn't need to be async at the moment - it's adding an extra layer for no real reason. It's logically equivalent to (but more complicated than):

public Task<List<TvChannel>> GetList()
{
    return new GetPrograms().DownloadTvChannels();
}

Jon, I want to get the items in the list asynchronously, so why is the async not appropriate on that GetList method ? Is it because I need to collect the items in the list async' and not the list itself ? When I try to mark the Main method with async I get a "does not contain a static Main method..."
@danielovich: What does DownloadTvChannels() return? Presumably it returns a Task<List<TvChannel>> doesn't it? If not, it's unlikely that you'd be able to await it. (Possible, given the awaiter pattern, but unlikely.) As for the Main method - it still needs to be static... did you replace the static modifier with the async modifier perhaps?
yes, it returns a Task<..> like you said. No matter how I try to put async in the Main method signature it throws an error. I am sitting on VS11 preview bits!
@danielovich: Even with a void return type? Just public static async void Main() {}? But if DownloadTvChannels() already returns a Task<List<TvChannel>>, presumably it's already asynchronous - so you don't need to add another layer. It's worth understanding this carefully.
@nawfal: Looking back, I think it changed before VS2013 was released. Not sure if C# 7 will be changing that...
K
Kedrzu

Newest version of C# - C# 7.1 allows to create async console app. To enable C# 7.1 in project, you have to upgrade your VS to at least 15.3, and change C# version to C# 7.1 or C# latest minor version. To do this, go to Project properties -> Build -> Advanced -> Language version.

After this, following code will work:

internal class Program
{
    public static async Task Main(string[] args)
    {
         (...)
    }

T
Theodor Zoulias

As of C# 7.1 the following signatures are valid for the Main method.

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

So, now you can do async/await

static async Task Main(string[] args)
{
    Console.WriteLine("Hello Asyn Main method!");
    await Task.Delay(200);
}

C
Community

On MSDN, the documentation for Task.Run Method (Action) provides this example which shows how to run a method asynchronously from main:

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        ShowThreadInfo("Application");

        var t = Task.Run(() => ShowThreadInfo("Task") );
        t.Wait();
    }

    static void ShowThreadInfo(String s)
    {
        Console.WriteLine("{0} Thread ID: {1}",
                          s, Thread.CurrentThread.ManagedThreadId);
    }
}
// The example displays the following output:
//       Application thread ID: 1
//       Task thread ID: 3

Note this statement that follows the example:

The examples show that the asynchronous task executes on a different thread than the main application thread.

So, if instead you want the task to run on the main application thread, see the answer by @StephenCleary.

And regarding the thread on which the task runs, also note Stephen's comment on his answer:

You can use a simple Wait or Result, and there's nothing wrong with that. But be aware that there are two important differences: 1) all async continuations run on the thread pool rather than the main thread, and 2) any exceptions are wrapped in an AggregateException.

(See Exception Handling (Task Parallel Library) for how to incorporate exception handling to deal with an AggregateException.)

Finally, on MSDN from the documentation for Task.Delay Method (TimeSpan), this example shows how to run an asynchronous task that returns a value:

using System;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        var t = Task.Run(async delegate
                {
                    await Task.Delay(TimeSpan.FromSeconds(1.5));
                    return 42;
                });
        t.Wait();
        Console.WriteLine("Task t Status: {0}, Result: {1}",
                          t.Status, t.Result);
    }
}
// The example displays the following output:
//        Task t Status: RanToCompletion, Result: 42

Note that instead of passing a delegate to Task.Run, you can instead pass a lambda function like this:

var t = Task.Run(async () =>
        {
            await Task.Delay(TimeSpan.FromSeconds(1.5));
            return 42;
        });

u
user_v

In my case I had a list of jobs that I wanted to run in async from my main method, have been using this in production for quite sometime and works fine.

static void Main(string[] args)
{
    Task.Run(async () => { await Task.WhenAll(jobslist.Select(nl => RunMulti(nl))); }).GetAwaiter().GetResult();
}
private static async Task RunMulti(List<string> joblist)
{
    await ...
}

N
Nathan Phillips

To avoid freezing when you call a function somewhere down the call stack that tries to re-join the current thread (which is stuck in a Wait), you need to do the following:

class Program
{
    static void Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        List<TvChannel> list = Task.Run((Func<Task<List<TvChannel>>>)bs.GetList).Result;
    }
}

(the cast is only required to resolve ambiguity)


Thanks; Task.Run doesn't cause the deadlock of GetList().Wait, this answer shold have more upvotes...
K
Kristaps Koks
class Program
{
     public static EventHandler AsyncHandler;
     static void Main(string[] args)
     {
        AsyncHandler+= async (sender, eventArgs) => { await AsyncMain(); };
        AsyncHandler?.Invoke(null, null);
     }

     private async Task AsyncMain()
     {
        //Your Async Code
     }
}

T
Tom Charles Zhang

This is hypothetical but I am thinking:

static void Main(string[] args)
{
    var context = new Thread(() => /*do stuff*/);
    context.Start();
    context.Join();
}