210

I have this simple code:

public static async Task<int> SumTwoOperationsAsync()
{
    var firstTask = GetOperationOneAsync();
    var secondTask = GetOperationTwoAsync();
    return await firstTask + await secondTask;
}


private async Task<int> GetOperationOneAsync()
{
    await Task.Delay(500); // Just to simulate an operation taking time
    return 10;
}

private async Task<int> GetOperationTwoAsync()
{
    await Task.Delay(100); // Just to simulate an operation taking time
    return 5;
}

Great. This compiles.

But let’s say I have a console application and I want to run the code above (calling SumTwoOperationsAsync()).

static void Main(string[] args)
{
     SumTwoOperationsAsync();
}

But I've read that (when using sync) I have to sync all the way up and down:

Does this mean that my Main function should be marked as async?

Well, it can't be because there is a compilation error:

an entry point cannot be marked with the 'async' modifier

If I understand the async stuff , the thread will enter the Main function → SumTwoOperationsAsync → will call both functions and will be out. But until the SumTwoOperationsAsync

What am I missing?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • 6
    Here's a [proper solution in C# 7.1](https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main/) – nawfal May 30 '17 at 05:47
  • 3
    @nawful seemed to miss out the crucial link, so here it is ... https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7-1#async-main - In your visual studio 2017 project, go to project properties -> build -> advanced, then change your language version to 7.1 (or higher) – alv Sep 16 '17 at 08:02

4 Answers4

353

In most project types, your async "up" and "down" will end at an async void event handler or returning a Task to your framework.

However, Console apps do not support this.

You can either just do a Wait on the returned task:

static void Main()
{
  MainAsync().Wait();
  // or, if you want to avoid exceptions being wrapped into AggregateException:
  //  MainAsync().GetAwaiter().GetResult();
}

static async Task MainAsync()
{
  ...
}

or you can use your own context like the one I wrote:

static void Main()
{
  AsyncContext.Run(() => MainAsync());
}

static async Task MainAsync()
{
  ...
}

More information for async Console apps is on my blog.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    If it were **not** console app , does my observation ( last line of question) is correct ? The thread is stopeed at `SumTwoOperationsAsync `...right ? (assuming the Main calling method is not marked as async) – Royi Namir Jul 13 '13 at 13:17
  • 2
    I have an [intro on my blog](http://blog.stephencleary.com/2012/02/async-and-await.html) that explains how threads work with `async` via the "context". – Stephen Cleary Jul 13 '13 at 14:58
  • 1
    Re-reading your answer - are you saying that it is pointless to use async in a console app ? – Royi Namir Nov 04 '14 at 08:43
  • 1
    @RoyiNamir: No. It's not as common, though, unless you're consuming an async-only API. Console (and other desktop) apps are generally less caring about wasting threads. – Stephen Cleary Nov 04 '14 at 17:01
  • @StephenCleary Is this same as `Task.Run( async () => { ... async calls ... } ).Wait();` if I didn't want to add a second MainAsync method? – Terry Sep 07 '16 at 14:29
  • @Terry: You can use `Task.Run(..).GetAwaiter().GetResult()`; that will just block the main thread until the async work completes. `AsyncContext.Run` is different; it runs a "main loop" on the calling thread and executes all async work in a single-threaded context. – Stephen Cleary Sep 07 '16 at 17:19
  • The first way does not seem to handle exceptions nicely. Thrown exceptions miss the surrounding try/catch block. At least it crashes in my app for some reason... – user2173353 Nov 03 '17 at 12:46
  • 1
    @user2173353: No, it's guaranteed to be caught in a `catch`. If that's not what you're seeing, I recommend asking your own question with a minimal code example. – Stephen Cleary Nov 03 '17 at 13:01
  • @StephenCleary My bad. The exception is caught, just my exception handling code was throwing new exceptions. – user2173353 Nov 03 '17 at 13:53
  • For the record, another option for handling exceptions on a root trampoline is `AppDomain.CurrentDomain.UnhandledException += (o, e) => { /* ... */ };` – Glenn Slayden Jun 27 '18 at 21:53
  • 1
    @Terry In case [Stephen's comment](https://stackoverflow.com/q/17630506#comment66079665_17630538) wasn't clear (it took me a second…), the difference is that whereas `Task.Run(..).GetAwaiter().GetResult()` will allocate a new `Task` from the task pool (possibly spinning-up new threads or other resources) to run the app—and then ironically proceed to waste the currently executing thread on blocking on the `Task` it just created, Stephen's `AsyncContext` more efficiently co-opts the thread you've implicitly been provided– *i.e.*, that you're currently executing on in `Main`—for that purpose. – Glenn Slayden Jun 27 '18 at 22:09
  • MainAsync().GetAwaiter().GetResult(); praise you! – bresleveloper Dec 14 '18 at 09:18
102

Here is the simplest way to do this

static void Main(string[] args)
{
    Task t = MainAsync(args);
    t.Wait();
}

static async Task MainAsync(string[] args)
{
    await ...
}
Murilo Maciel Curti
  • 2,677
  • 1
  • 21
  • 26
4

As a quick and very scoped solution:

Task.Result

Both Task.Result and Task.Wait won't allow to improving scalability when used with I/O, as they will cause the calling thread to stay blocked waiting for the I/O to end.

When you call .Result on an incomplete Task, the thread executing the method has to sit and wait for the task to complete, which blocks the thread from doing any other useful work in the meantime. This negates the benefit of the asynchronous nature of the task.

notasync

Alberto
  • 783
  • 1
  • 7
  • 15
1

My solution. The JSONServer is a class I wrote for running an HttpListener server in a console window.

class Program
{
    public static JSONServer srv = null;

    static void Main(string[] args)
    {
        Console.WriteLine("NLPS Core Server");

        srv = new JSONServer(100);
        srv.Start();

        InputLoopProcessor();

        while(srv.IsRunning)
        {
            Thread.Sleep(250);
        }

    }

    private static async Task InputLoopProcessor()
    {
        string line = "";

        Console.WriteLine("Core NLPS Server: Started on port 8080. " + DateTime.Now);

        while(line != "quit")
        {
            Console.Write(": ");
            line = Console.ReadLine().ToLower();
            Console.WriteLine(line);

            if(line == "?" || line == "help")
            {
                Console.WriteLine("Core NLPS Server Help");
                Console.WriteLine("    ? or help: Show this help.");
                Console.WriteLine("    quit: Stop the server.");
            }
        }
        srv.Stop();
        Console.WriteLine("Core Processor done at " + DateTime.Now);

    }
}
Richard Keene
  • 398
  • 3
  • 14