1

I'm trying to figure out how an async main method is started in C#. To do this I would like to create an example of a new Thread that is the same as the async main thread.

This is how it is done without async:

class Program
{
    public static void Main(string[] args)
    {
        Thread t = new Thread(Main2)
        { IsBackground = false };
        t.Start();
    }

    public static void Main2()
    {
        Console.WriteLine("Helloooo");
        Thread.Sleep(1000);
        Console.WriteLine("Woooorld");
    }
}

If I run the code above, the following is printed:

Helloooo
Woooorld

You can see that I don't add a t.Join() that's because t is a foreground thread. But if try to do the same with async the following happens:

class Program
{
    public static void Main(string[] args)
    {
        Thread t = new Thread(Main2)
        { IsBackground = false };
        t.Start();
    }

    // Can't use "public static async Task main2"
    // because you need to pass in a void method to a new thread
    public static async void Main2()
    {
        Console.WriteLine("Helloooo");
        await Task.Delay(1000);
        Console.WriteLine("Woooorld");
    }
}

Only

Helloooo

Is printed, and the program exits, even though the new thread is a foreground thread. Now I understand what is happening here, When the new thread reaches await Task.Delay(1000); It starts up a state machine and releases the thread for other things. What I want to know is what I have to change to let my original thread die, and let my new thread take over. I want to understand how the async/await chain gets started.

NomenNescio
  • 2,899
  • 8
  • 44
  • 82
  • 1
    Why are you using a thread if what you've got is a task? What are you intending to achieve? The proper way to do this is to make `Main2` return `Task` (not `void`) and `await` it in `Main` (which should then also become `async Task`). – Jeroen Mostert Oct 24 '22 at 12:53
  • I think you should read more about what async is and how the methods break-up into statemachines. – Jeroen van Langen Oct 24 '22 at 12:56
  • 1
    @JeroenMostert I want to understand how the async/await chain gets started. Is what is calling ```public static async Task Main``` also calling ```await Main()```, and is the function calling that also using await? How is it started? How can I start my own? – NomenNescio Oct 24 '22 at 12:58
  • 1
    Note that your title asks something different than your question body: Your title mentions an "static async Task Main()", but none of the methods in your question body returns a `Task`. – Heinzi Oct 24 '22 at 12:59
  • How is it started? Well, either through something like `Task.Run` if you're doing it yourself or the equivalent in the compiler when it sees your `Main` returns a `Task`. – Jeroen Mostert Oct 24 '22 at 13:03
  • Related: [Is it ok to use "async" with a ThreadStart method?](https://stackoverflow.com/questions/44364092/is-it-ok-to-use-async-with-a-threadstart-method) – Theodor Zoulias Oct 24 '22 at 13:14

2 Answers2

2

An article about C# 7.1 says:

"Allow await to be used in an application's Main / entrypoint method by allowing the entrypoint to return Task / Task and be marked async."

public static void Main()
{
    MainAsync().GetAwaiter().GetResult();
}

private static async Task MainAsync()
{
    ... // Main body here
}

"We can remove the need for this boilerplate and make it easier to get started simply by allowing Main itself to be async such that awaits can be used in it."

Source: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.1/async-main

As you can see, they don't spin up a separate thread, but "blocking" the current thread until the MainAsync returning task IsCompleted.

Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • So is it then correct to say that all C# async programs have a minimum of 2 threads, one of them always blocking(assuming somewhere in the main async is called)? – NomenNescio Nov 09 '22 at 17:57
1

This happens because of the following combination of factors:

  • In a Console application, SynchronizationContext.Current not is set.
  • If SynchronizationContext.Current not is set, await responses (e.g. printing "Woooorld" in your example) are invoked on a ThreadPool thread.
  • ThreadPool threads are background threads.
Heinzi
  • 167,459
  • 57
  • 363
  • 519