Calling Async Code from Main Without Hiding a Blocking Wait

Image retained from the original article.

Originally published 10 October 2023; rewritten and reviewed on 15 October 2026.

A console application often starts with a short Main method. Later, a network call or file operation becomes asynchronous, and the entry point needs a decision. The original example used GetAwaiter().GetResult(). Its most important behaviour is that it blocks the calling thread.

Prefer an asynchronous entry point when supported

Microsoft documents async Task Main and async Task<int> Main as supported entry-point forms. A small example makes the control flow explicit:

using System;
using System.Threading.Tasks;

internal static class Program
{
    private static async Task Main()
    {
        string greeting = await GetGreetingAsync();
        Console.WriteLine(greeting);
    }

    private static Task<string> GetGreetingAsync() =>
        Task.FromResult("Hello from the console application");
}

Task.FromResult returns an already completed task. It demonstrates the shape of the code; it does not simulate a delayed network operation or prove a performance improvement.

Keep a synchronous bridge at a deliberate boundary

In an older application that cannot change its entry-point signature, GetGreetingAsync().GetAwaiter().GetResult() waits for completion synchronously. That may be a compatibility decision at the application’s outer boundary. It should not become a habit copied into UI handlers or request-processing code, where blocking creates different problems.

Check the language version and project target before choosing either form. Handle failures at a boundary where a useful error and exit code can be produced.

Test completion and failure

Run the program with both a successful operation and a task that throws. Confirm that it waits for the required work and reports failure predictably. Starting a task and allowing Main to return early is not a substitute for awaiting it.

My preference is to keep asynchronous flow visible from the entry point. It leaves fewer surprises for the next person tracing why the process is waiting.

Related reading