0
votes

Can someone please explain why the public async Task DoStuff() method below can still work without returning anything? It doesn't say void, so I assumed the return type would have to be Task.

When I remove the async and await keywords from the DoStuff() method, the compiler gives me a "not all code paths return a value" error. However, if I add the async and await keywords, it doesn't seem to need a return type, despite the lack of the void keyword in the method signature. I don't get it!

What exactly IS a Task? Microsoft explains it really poorly. Thanks.

namespace Async_and_Await_Example
{
    class Program
    {
        static void Main(string[] args)
        {
            AsyncAwaitDemo demo = new AsyncAwaitDemo();
            demo.DoStuff();

            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("Working on the Main Thread...................");
        }
    }
}
public class AsyncAwaitDemo
{
    public async Task DoStuff()
    {
        await Task.Run(() =>
        {
            CountToFifty();
        });
    }

    private static async Task<string> CountToFifty()
    {
        int counter;

        for (counter = 0; counter < 51; counter++)
        {
            Console.WriteLine("BG thread: " + counter);
        }

        return "Counter = " + counter;
    }
}

}

6

6 Answers

2
votes

Why the public async Task DoStuff() method below can still work without returning anything?

Because the compiler allows it to. When you mark a method with the async modifier, a state-machine is created which actually returns a Task for you. You can see it in any de-compiler.

When I remove the async and await keywords from the DoStuff() method, the compiler gives me a "not all code paths return a value" error.

Because there is no longer a state machine created which returns a Task, you have to do it by yourself now, since there is no more compiler magic.

What exactly IS a Task?

As others said, simply a promise of work which will complete in the future. A Task can represent many things, one of them is an asynchronous operation. The magic lays with the async-await keywords that come alongside. The compiler magic has a special relationship with Task, but any type implementing a GetAwaiter method can be awaited. More on that here

2
votes

A task is essentially a promise (or future). It “promises” you that the asynchronous method you started will eventually finish, completing the task. The task object is used so that you can get information about when the task is done. There is also the generic version of a task which simply also promises that you get a value when the task is finished.

Once you started an asynchronous method, you get a Task object back. The method returns pretty much instantly but the actual work likely finishes later. You can then wait for the task to complete to block the current thread and simply wait until the asynchronous method is done.

When you are in an asynchronous execution yourself—which is usually what you want to do when calling asynchronous methods—then you can await those tasks using the await keyword on the task object. This essentially pauses the asynchronous method you are currently in and returns the execution as soon as the task you are awaiting completes.

The await keyword is only available in asynchronous methods, signalized by the async method specifier. Those methods will automatically return a task object for the return value: If you don’t return anything explicitely from the asynchronous method, it returns a Task object; if you return an object of type T from the asynchronous method, it actually returns a Task<T> object which you can await for. Task<T> types can then be “unpacked” once the task is done. This allows you to get the actual object of type T.

Finally, async methods can also return nothing, void, to make them “fire and forget”. If you call an asynchronous method that has a return type of void, it will be executed asynchronously (“fire”) but you won’t have any way of knowing when it finishes since you have no task object to wait for (“forget”). This is why you want to avoid async void methods in general (they are also bad for exception handling) and always use “real” awaitable asynchronous methods instead (those that return some task object). But you can still use an async void method to start the asynchronous execution without blocking your main thread. Otherwise, you can just block it by calling the Wait() method on the task.

For more information, check the following links:

0
votes

It's because async/await is magic. OK it's not really magic, but with async/await the compiler is re-writing your method so that it returns a Task. Just like CountToFifty() returns a Task<string> but your method returns a string.

A Task itself is nothing special. It's just an ordinary .Net class that is used to represent an operation that may not be completed already.

Without async/await the compiler doesn't modify DoStuff() so it's own you to return a Task object.

0
votes

As Async Methods do not immediately return. A method may need to query an external source. This takes time—and other code could run. That is purpose of await-async in Method. So it is not always necessary to return in Async Method when we use await keyword.

Check Explanation of Tasks by DotNetPearls for more.

0
votes

As others have noted, a task is a "promise" or "future" - that is, it represents some operation that may complete in the future.

Task represents operations without return values, and Task<T> represents operations with a return value of T.

Note that Task is useful (instead of void), because the operation may complete either successfully or with an exception (or cancelled), and Task is capable of representing these end states.

Can someone please explain why the public async Task DoStuff() method below can still work without returning anything?

The async keyword will construct a state machine for your method, and the state machine will create a Task object that represents that method. If your async method returns a value, the state machine places that return value on the Task<T>. If your async method throws an exception, the state machine places that exception on the Task/Task<T>.

What exactly IS a Task?

I've described what Task is in the context of async/await. Part of the confusion comes in because Task takes on a very different meaning when used in alternate ways. I use the terms Promise Task for asynchronous tasks and Delegate Task for code-running tasks.

Tasks created by an async state machine are always Promise Tasks. Generally speaking, asynchronous tasks should be await'ed, not Wait'ed. And you should avoid async void (since there's no way for the state machine to represent the method, it has surprising exception handling semantics).

You may find my async intro helpful as well as my article on best practices.

0
votes

Another way of looking at this is let's say you have 4 things to do in response to an http request from a customer.

  1. Enter an order in the database. Task.Run(PlaceOrder)
  2. Send the customer a thank you email. Task.Run(SendEmail)
  3. Send a request to your fulfillment center api. Task.Run(PlaceOrder)
  4. Log the session request. Task.Run(LogRequest)

So rather than talking about contracts, promises, state servers, etc. a Task is threading that spreads the work across all your cores/processors. I have 4 cores and 8 processors. If I Task the above I can see all 8 processors going and it's more than twice as fast. That's a Task.

It get's even better. The work is running on all 8 processors now but we can make it even faster. Although they are running on all 8 processors they take turns or stand in line waiting for each other to finish. Let's say on average each Task takes 5 seconds so my response time to the customer is 20 seconds but we just cut that in half. If I use async/await I can push all 4 Tasks at the same time so none of them wait for each other. So now I can see all 8 processors going but now their using a little more cpu and wow64 their done.

But wait they were still slow because the api request to ship the order took 8 seconds and the customer and the other Tasks had to wait for it to finish. This is where it gets really good. Don't wait. Respond to the customer and thank them for the order. So now you just turned a transaction back to the customer in 3 milliseconds rather than 20 seconds. Wrap a try/catch around the 4 Tasks and log any fails so someone can work them. Hopefully, that is already being done rather than telling the customer oh, sorry our mail server is down. I've heard some folks calling that forget and something else and that's very bad. It's not bad. It's good programming. Solid mainstream threading/asynchronous programming is way overdue. Buying cores and memory that never get used and users dealing with a slow response waiting for things frankly they don't care will hopefully will be a thing of the past.

That's task/async/await.

var myTask = Task.Run(() => { doSomething });.