0

I'm attempting to mimic the same output for two different implementations asynchronous functionality - the only difference in my mind being return mechanisms. I've boiled down the two examples to the most abstract example I can make them.

For example, the first implementation is very simplistic and behaves as I would expect:

Main()
{
    DoStuff();
    Console.WriteLine("Hello");
}

async void DoStuff()
{
    try
    {
        string output = await Task.Run(() => 
        {
            return longRunningMethod();
        });

        Console.WriteLine(output);                            
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

string longRunningMethod()
{
    Console.WriteLine("Sleeping...");
    Thread.Sleep(2000);
    Console.WriteLine("... woken up!");
    return "World!";
}

/*--------- OUTPUT----------
 * Hello
 * Sleeping...
 *...woken up!
 * World!

I then thought to myself that it would be more useful if you could use the result of the async method in a method call rather than return void and embed the behaviour into the void method itself.

Using some reference material and muddling along I have something in my mind that I thought would work but, as below, is returning in the incorrect order.

Main()
{
    Console.WriteLine(DoStuff().Result);
    Console.WriteLine("Hello");
}

async Task<string> DoStuff()
{
    try
    {
        return await Task.Run(() =>
        {
            return longRunningMethod();
        });               
    }
    catch(Exception ex)
    {
        return ex.ToString();
    }
}    

string longRunningMethod()
{
    Console.WriteLine("Sleeping...");
    Thread.Sleep(2000);
    Console.WriteLine("... woken up!");
    return "World!";
}

/*--------- OUTPUT----------         
 * Sleeping...
 *...woken up!
 * World!
 * Hello

In my mind the two snippets should basically be synonymous though this is clearly not the case. An error on my part is causing the code to run synchronously.

Either I'm being dumb or I'm fundamentally misunderstanding something.

PS. If anyone has any advice titling a question like this, feel free to edit/comment as I'm aware it's not particularly informative.

James
  • 356
  • 2
  • 13
  • The second piece of code will block until the async method has completed, because you used `.Result`, as such Hello will not be written before it has completely executed this async method. The first piece of code starts the async method, but since it spawns a long-running background thread that is awaited, control is returned and Hello is written first. – Lasse V. Karlsen Jan 02 '20 at 14:28
  • 1
    In short, you should not Wait for tasks by calling Wait, nor should you use .Result, and there should be plenty of articles on the web already explaining why (not). – Lasse V. Karlsen Jan 02 '20 at 14:29
  • With your fix wouldn't the console application exit before `DoStuff` finishes because you are never waiting for it to finish? – Rand Random Jan 02 '20 at 14:54
  • @RandRandom correct - as tasks are background processes (as pointed out by Kasse), it was a result of abstracting the problem that I neglected to put a `ReadKey` back in to prevent the early termination. Thanks – James Jan 02 '20 at 15:10

3 Answers3

2

If your Main method were an actual Main in the first example, your output could be different. On my machine I get either

> Hello

or

> Sleeping...
> Hello

or

> Hello
> Sleeping...

Depending on ordering of the instructions and whether the other thread actually manages to run the WriteLine before the program terminates.

That is because you're firing a task that is never awaited by anyone. Creating a method that is expected to complete asynchronously but returns void is a very. Very. Bad. Idea.

What happens, in simple terms, is:

  1. you go into DoStuff from Main;
  2. DoStuff creates a Task that represents the execution of the longRunningMethod on the thread pool (these are the semantics of Task.Run);
  3. DoStuff awaits that Task - since it hasn't completed yet, a bunch of hard complicated stuff is done by the async machinery and the control returns to the caller, which is Main;
  4. back in Main, DoStuff returned - that's it, we advance to the next instruction, print Hello and return from the Main;
  5. what happens now depends on many things, but on a run-of-the-mill PC with Windows 10 the app is terminated and all of its threads mercilessly killed - that includes the thread that was dutifully running the longRunningMethod

The take-away about void returning async methods is - don't write them.

In your second example, you're actually waiting for the Task returned by DoStuff, synchronously, which means that when at point 4. in the above description you actually take the Task and block on it until it gets completed. Therefore the longRunningMethod gets to complete, return, DoStuff gets the control back, completes, returns, and unblocks the Main.

To emulate the semantics you want, here's what you can do:

void Main()
{
    Task<string> stuff = DoStuff();
    Console.WriteLine("Hello");
    Console.WriteLine(stuff.Result);
}

You start a Task, do some work and then synchronously block on the async work. Doing that is also bad.

To get to the sweet, idiomatic C# code, you need to use the async Main feature from C# 7.1, which allows you to declare Main as an async Task method:

async Task Main()
{
    Task<string> stuff = DoStuff();
    Console.WriteLine("Hello");
    Console.WriteLine(await stuff);
}

Voila, blocking removed. The async Main doesn't really change that much, especially in a simple console app, but it's good taste to use async/await all the way. It also serves as a pedagogical example in case you're running DoStuff from something other than Main - then it can make a world of a difference.

V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • After a quick google search of the word "pedagogical" this answer is very informative. I'm leafing through the references you provided but will remove my "fix" edit for now as it uses the dreaded `async void`. Many thanks – James Jan 02 '20 at 15:21
1

You are fundamentally misunderstanding something. To quote MSDN. https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/async-return-types

The Result property is a blocking property. If you try to access it before its task is finished, the thread that's currently active is blocked until the task completes and the value is available. In most cases, you should access the value by using await instead of accessing the property directly. The previous example retrieved the value of the Result property to block the main thread so that the ShowTodaysInfo method could finish execution before the application ended.

TJ Rockefeller
  • 3,178
  • 17
  • 43
  • Marked as answer due to the reference material. Will update my question with a "this is how I fixed it based on the answer" edit. Thanks also to @Euphoric – James Jan 02 '20 at 14:43
  • @James You can get "how I will have fixed it" from my answer. – V0ldek Jan 02 '20 at 14:55
0

Accessing Result on Task blocks the calling thread until the tasks finishes so it can return it's result.

It is generally discouraged to call Result for any reason.

Euphoric
  • 12,645
  • 1
  • 30
  • 44