0

I am failing to understand why calling a async method from the Main method from a console application hangs forever. I feel I am missing some key element of asynchronous execution:

static void Main(string[] args)
{
    Console.WriteLine("w3");
        
    var exe = new Exe2();
    exe.Do2().Wait();
        
    Console.WriteLine("/w3");
    Console.ReadKey();
}

public class Exe2
{
    public async Task Do2()
    {
        Task task1 = new Task(() => { Console.WriteLine("t1"); Task.Delay(2000); });
        Task task2 = new Task(() => { Console.WriteLine("t2"); Task.Delay(2000); });
        Task task3 = new Task(() => { Console.WriteLine("t3"); Task.Delay(2000); });

        await Task.WhenAll(task1, task2, task3);
    }
}

The code above prints w3 not nothing else, and doesn't take the ReadKey either.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
AmandaSai98b
  • 485
  • 1
  • 7
  • 17

1 Answers1

2

You haven't started your tasks, Task.WhenAll is essentially going to wait forever. You would have needed to call Start

Starts the Task, scheduling it for execution to the current TaskScheduler.

task1.Start();
task2.Start();
task3.Start();

await Task.WhenAll(task1, task2, task3);

However : NEVER use the Task constructor unless you absolutely know you need it, it has several very big traps for unseasoned coders such as only supporting the action delegate and needing to be started. Instead ALWAYS use Task.Run which will start your tasks hot

Task task1 = Task.Run(() => { Console.WriteLine("t1"); Task.Delay(2000); });
Task task2 = Task.Run(() => { Console.WriteLine("t2"); Task.Delay(2000); });
Task task3 = Task.Run(() => { Console.WriteLine("t3"); Task.Delay(2000); });

Although not your problem, you should NEVER need to call Wait or Result in modern .net and even more so on an async method. Instead, use the async Main overload

static async Task Main(string[] args)
{
    Console.WriteLine("w3");
    
    var exe = new Exe2();
    await exe.Do2();
    
    Console.WriteLine("/w3");
    Console.ReadKey();
}
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • *"**NEVER** use the Task constructor unless you absolutely know you need it"* <== Strong wording followed by an exception to the rule, defeats the purpose of strong wording. – Theodor Zoulias Oct 13 '21 at 05:15
  • 1
    @TheodorZoulias yeah I run out of capitals and emphasis. I was going to leave it as that, then thought *"well... this one time at band camp"* – TheGeneral Oct 13 '21 at 05:20
  • I keep hearing the term modern dotnet and yet no clear info out there about it. Is is just a language converstion to express how to use .net nowdays? even if so, is there an offical resource of guidelines? – AmandaSai98b Oct 13 '21 at 08:27
  • @AmandaSai98b .net has changed a lot over the years, we now have native and first class support for the async and await pattern, we seldom need to use `Result` and `Wait` to wait for a task, we just use the keyword `await` – TheGeneral Oct 13 '21 at 08:29
  • @AmandaSai98b in short there is actually no such thing as modern .net, its just there are more modern ways of doing things – TheGeneral Oct 13 '21 at 08:30
  • @TheGeneral you re right, the constructor of Task takes Action delegate type only. No Func, No Predicate. Wondering why that is. You have a point there that this give us less flexibility but I can't agree at the moment with the statement we should Never use it. HAving a instance of a task created and start it a later, use as parameter, etc, sounds like a common requirement to me. Wouldn't you agree? – AmandaSai98b Oct 13 '21 at 08:39
  • @AmandaSai98b there are situations where it may be useful, which is why I added the clause to the end of the statement. However, its really hard to justify ever needing to allocate a task to start it later. Even the task options are rare to need these days. If you need to start a task in the future, just spin one up in the future. Though i agree this still should be a feature of the language, however and unfortunately its a very common trap for young players – TheGeneral Oct 13 '21 at 08:48
  • @TheGeneral thanks for the conversation. I think I can agree regarding the use of non-started Tasks. It feels like the need for doing that could be easily replaced, and propertly used, delegate types. – AmandaSai98b Oct 13 '21 at 08:57
  • @TheGeneral also, a case for the taskInstance.Start over Task.Run is the former runs the async code in the same executing thread and lets the OS or context decide if if should run a one or more threads. Task.Run, from what I can tell, will always run in a ThreadPool – AmandaSai98b Oct 13 '21 at 09:11
  • @AmandaSai98b yes there is also more to this and what can be done with tasks in general. though in most uses cases they are seldom needed, however if you are fully aware of what you need and want, it can be useful – TheGeneral Oct 13 '21 at 09:18