Home > Software design >  Async and Await in C# not properly understood
Async and Await in C# not properly understood

Time:08-05

In my command line program, the user can execute either two asynchronous functions (selection 1) or two synchronous functions (selection 2) via a selection loop. Unfortunately, the execution of the asynchronous functions behaves exactly the same as the synchronous functions: The selection loop freezes until the tasks are finished. Maybe I have a wrong understanding of await but shouldn't it ensure that the execution of the program continues. I now firmly expect the "Hello World" output to be displayed while the tasks are still busy. Where is my error in thinking?

await main();

static async Task main()
{
    string input="start";
    while(input!="exit"){
        Console.WriteLine("choose an option:");
        Console.WriteLine("1 asynchrone");
        Console.WriteLine("2 synchrone");
        Console.WriteLine("exit.");
        input=Console.ReadLine();
        switch(input){
            case "1":await startTaskAsync();Console.WriteLine("Hello World");break;
            case "2":startTaskSync(); break;
            case "exit":input="exit"; break;
            default:input="exit";break;
        }

    }
}  

static async Task startTaskAsync(){
    var FrogTask=startFrogAsync();
    var  MouseTask=startMouseAsync();
    List<Task> allTask=new List<Task>(){FrogTask,MouseTask};
    Task.WaitAll(MouseTask,FrogTask);
    Console.WriteLine(FrogTask.Result);
    Console.WriteLine(MouseTask.Result);   
}

static void startTaskSync(){
    string result1=startFrogSync();
    string result2=startMouseSync();
    Console.WriteLine(result1);
    Console.WriteLine(result2);   
}



static async Task<string> startMouseAsync(){
    string result ="startMouseAsync completed";
    Thread.Sleep(2000);
    return result;
}

static async Task<string>  startFrogAsync(){
    string result ="startFrogAsync completed";
    Thread.Sleep(10000);
    return result;
}

static string startMouseSync(){
    string result ="startMouseSync completed";
    Thread.Sleep(2000);
    return result;
}

static string startFrogSync(){
    string result ="startFrogSync completed";
    Thread.Sleep(10000);
    return result;
}

CodePudding user response:

Your awaiting the task before you write out Hello world, that means that you tell it to await the completion of that task before you continue execution.

await startTaskAsync();
Console.WriteLine("Hello World");

Instead you should have something as:

var myTask = startTaskAsync();
Console.WriteLine("Hello World");
await myTask;

Another problem is in the StartTaskAsync method where you use WaitAll instead of WhenAll, this again ends up making this a synchronous method.

Instead do:

static async Task StartTaskAsync(){
    string[] results = await Task.WhenAll(startMouseAsync(),startFrogAsync());
    foreach (string result in results)
        Console.WriteLine(result);
}

And then I'll second the "Don't use Thread.Sleep". Right now, neither your startMouseAsync or startFrogAsync are actually async, most IDE's would warn you about that AFAIK.

While you can simply add await Task.Yield(); to in those methods to force them to release control of the thread, I would greatly discourage that, and even then using Thread.Sleep is still bad as it "holds" a thread instead of releasing it where as await Task.Delay will release the thread.


As it turns out however, using Thread.Sleep is very good at demonstrating a certain thing about async / await. And if you know you have some heavy computational stuff to do in a place, you can somewhat simulate the behavior with Thread.Sleep/Thread.SpinWait.

Try implement your startFrogAsync and startMouseAsync as:

static async Task<string> startMouseAsync()
{
    await Task.Yield();
    Thread.Sleep(2000);
    string result ="startMouseAsync completed";
    return result;
}

vs

static async Task<string> startMouseAsync()
{
    Thread.Sleep(2000);
    await Task.Yield();
    string result ="startMouseAsync completed";
    return result;
}

Here you will notice that the last one does not seem to work either, this is because you perform the "heavy work" before you release control of the thread, said in another way, up until the await Task.Yield();, your code actually runs synchronously.


All in all, the above use of Yield is simply to demonstrate some points, I would personally never advocate for using yield.

You may use Task.Run or similar if you have some intensive work you need to offload to a seperate thread on the threadpool. Other scenarios where you use async/await in code includes waiting for external resources such as DB Queries, Web Requests, IO Requests etc.

CodePudding user response:

Only use Thread.Sleep for your "sync" functions at the end. For your async functions use await Task.Delay(xx) as previously noted.

await startTaskAsync() ...you're telling the user input code to wait until that async task is finished. If you want it to run in the background just do this: Task runAsync = startTaskAsync() i.e. without the await

You also want to note the compiler warning that your async tasks will run synchronously, which is alerting you to this fact.

This async method lacks 'await' operators and will run synchronously

And so, you will also need to change your Task.WaitAll (which is a non-async function) to await Task.WhenAll. Async tasks need awaits in them.

CodePudding user response:

OK, there's a couple of things going on here.

Firstly - as JL0PD - said, don't mix Thread.Sleep in async code, you can/will create hard to find bugs in production code.

Also, you're using Task.WaitAll which - counter intuitively - does a synchronous wait on all the tasks.

After that, in startTaskAsync you're hiding which method finishes first.

static async Task startTaskAsync(){
    var FrogTask=startFrogAsync();
    var  MouseTask=startMouseAsync();
    List<Task> allTask=new List<Task>(){FrogTask,MouseTask};
    Task.WaitAll(MouseTask,FrogTask);
    Console.WriteLine(FrogTask.Result);
    Console.WriteLine(MouseTask.Result);   
}

You wait for them both, and then you write the output of both, in the same order, always.

What I would do, is have the actual Frog or Mouse method do the write, then you'll see each in the order it executes.

Try this (I've also shortened the delays).

await main();

static async Task main()
{
    string input = "start";
    while (input != "exit")
    {
        Console.WriteLine("choose an option:");
        Console.WriteLine("1 asynchrone");
        Console.WriteLine("2 synchrone");
        Console.WriteLine("exit.");
        input = Console.ReadLine() ?? "";
        switch (input)
        {
            case "1": 
                await startTaskAsync(); 
                break;
            case "2": 
                startTaskSync(); 
                break;
            case "exit":
                return;
            default: 
                break;
        }

    }
}

static async Task startTaskAsync()
{
    var FrogTask = startFrogAsync();
    var MouseTask = startMouseAsync();
    await Task.WhenAll(MouseTask, FrogTask);
}

static void startTaskSync()
{
    startFrogSync();
    startMouseSync();
}

static async Task startMouseAsync()
{
    await Task.Delay(200);
    Console.WriteLine("startMouseAsync completed");
}

static async Task startFrogAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("startFrogAsync completed");
}

static void startMouseSync()
{
    Thread.Sleep(200);
    Console.WriteLine("startMouseSync completed");
}

static void startFrogSync()
{
    Thread.Sleep(1000);
    Console.WriteLine("startFrogSync completed");
}
  • Related