Lijin Durairaj
Lijin Durairaj

Reputation: 5240

understanding Async and Await in C#

I am trying to learn Async and Await in C#. I have three methods and when I try to call all this three methods it makes me wonder how the Asynchronous call actually works in C#.

class Content {            

    public async Task<string> Delay1() {
        await Task.Delay(5000);
        return "hello";
    }
    public async Task<string> Delay2() {
        await Task.Delay(5000);
        return "hello";
    }
    public async Task<string> Delay3() {
        await Task.Delay(5000);
        return "hello";
    }
    public async Task Print() {
        Console.WriteLine(await Delay1());
        Console.WriteLine(await Delay2());
        Console.WriteLine(await Delay3());
    }
}

So, When I make a call to this method:

static void Main() {
    new Content().Print();
    Console.Read();
}

I am supposed to get all the three methods at the same time after a delay of 5 seconds but instead my Delay2() method gets called after 5 seconds of the Delay1() method and Delay3() method gets called after 5 seconds of the Delay2() method.

Can someone help me understand the working of Async method in C#?

UPDATE 1 Async and Await are suppose to be Asynchronous, then why is my regular statement getting stopped from execution

public async Task Print()
        {
            Console.WriteLine(await Delay1());

            Console.WriteLine("this is regular flow");
        }

Upvotes: 2

Views: 4123

Answers (5)

Nelson
Nelson

Reputation: 1757

Asynchronous is not concurrent. Each call to await will prevent the code following from being executed until the Task has completed. In this case, you can create concurrency by changing your example to look like this

class Program
{
    static void Main()
    {
        new Content().Print().Wait();
        Console.Read();
    }
}

class Content
{

    public async Task<string> Delay1()
    {
        await Task.Delay(5000);
        return "hello";
    }
    public async Task<string> Delay2()
    {
        await Task.Delay(5000);
        return "hello";
    }
    public async Task<string> Delay3()
    {
        await Task.Delay(5000);
        return "hello";
    }

    public async Task Print()
    {
        var tasks = new[] {Delay1(), Delay2(), Delay3()};
        var results = await Task.WhenAll(tasks);
        foreach(var result in results)
        {
            Console.WriteLine(result);
        }
    }
}

You can start three Tasks independently of one another and store them in a collection. Then, you can call await Task.WhenAll to block execution until all of those tasks have completed. Afterwards, you can loop through the results and use them however you want.

Upvotes: 2

CodingYoshi
CodingYoshi

Reputation: 27039

Update with citations

Since my answer has been downvoted for reasons I am not aware of, since downvoters did not give any reasons for the downvote, except for Rob, I am here to defend my answer.

The point here is not to prove anyone is wrong but to make sure OP receives correct information and facts.

So what was OPs question? Two questions in my opinion:

  1. Can someone help me understand the working of Async method in C#? (Explicity asked)
  2. Why is my code not doing what I think it should be doing?

There were many answers posted to fix the code. Nelson's answer has correct code as well, hence I never downvoted. My issue is not with the solutions provided here but with the statement that "Asynchronous is NOT concurrent". I also wanted to explain how async-await works, using plain English, since the OP explicitly asked for this. So here is why I still stand my ground:

Stephen Cleary, a well respected author and SO member states this in his book on concurrency:

Asynchronous Programming

A form of concurrency that uses futures or callbacks to avoid unnecessary threads.

A future (or promise) is a type that represents some operation that will complete in the future. The modern future types in .NET are Task and Task. Older asynchronous APIs use callbacks or events instead of futures. Asynchronous programming is centered around the idea of an asynchronous operation: some operation that is started that will complete some time later. While the operation is in progress, it does not block the original thread; the thread that starts the operation is free to do other work. When the operation completes, it notifies its future or invokes its completion callback event to let the application know the operation is finished.

He goes on further to say:

Asynchronous programming is a powerful form of concurrency, but until recently, it required extremely complex code. The async and await support in VS2012 make asynchronous programming almost as easy as synchronous (nonconcurrent) programming.

Here is a stackoverflow answer wherein the same author has answered another similar question.

This is the link to the book and the part of I have quoted is from 1.1. Introduction to Concurrency.

Now that should clarify and support my statement on asynchronous being concurrent.

I am not sure how my "examples have nothing to do with concurrency" but perhaps Rob can chime in and explain that.

I am not sure what the other misleading parts are in my answer so I cannot defend myself. I DO NOT think there are any misleading statements I have made but if I have, I, the OP and other members would love to know them so it can benefit as all.

End of Update with citations

.

It is NOT true as some other answers have stated that async is not a form of concurrency. Ansynchronous programming IS (Yes it is) a form of concurrency. But, having said that, it does not mean the work will need less time. The work will still take the same amount of time (possibly longer due to context switching). But the trick is the work will be done at the same time without waiting for previous work to finish (Delay1 then Delay2 and then Delay3). Let me explain all this using your code.

Here is what you are doing in your code in Print method: Lets call the thread executing Print P1

  1. Do Delay1 AND when it returns then go to 2 below. In Delay1 method another thread is given the task to sleep. Lets call it T1. T1 please sleep for 5 secs and then return "hello".
  2. Do Delay2 AND when it returns then go to 3 below. In Delay2 method another thread is given the task to sleep. Lets call it T2. T2 please sleep for 5 secs and then return "hello".
  3. Do Delay3 AND when it returns then go to next line (there is no next line so program will complete). In Delay3 method another thread is given the task to sleep. Lets call it T3. T3 please sleep for 5 secs and then return "hello".

So your print method method calls Delay1 and waits for it to return. Then it calls Delay2 and waits for it to return. Then 3 and waits for it to return. All those will return after 5 secs of sleeping (each).

So you may ask: "then what is the point of all this"?

The point is that in your print method you can call all of them at the same time: do Delay1, do Delay2 and do Delay3 and when they are all done then go to next line.

So what does that get you?

Well if you were calling some web service in your Delay methods and each one takes 10 seconds to return, you are calling them ALL AT THE SAME time (almost) so all three will start getting to work. Chances are they will all return after a little over 10 seconds. So you basically sent them all to work at the same time hence "Concurrency". If you do not call them asynchronously, then Delay1 needs to complete (10 Secs), then Delay2 (10 secs) then Delay3 (10 secs) so 30 seconds all in total.

Think about it like this:

You are a manager at McDonald's. You have many employees (thread pool) and you need them to do work (Delay1, Delay2 and Delay3). What is better to tell 1 employee to do Delay1 then Delay2 and then Delay3 or assign 3 different employees (threads from thread pool) and ask 1 employee to do Delay1 and tell second employee to do Delay2 and not to wait for Delay1 to complete and ask 3rd employee to do Delay3. Obviously you would choose 3 employees.

Now in the world of thread pools it is a bit different. It is possible that the the same thread is chosen for 2 tasks because one thread did the job and came back to the pool and then was given another task. In your example, it is highly unlikely because they need to sleep for 5 secs. There is only 1 way to do that.

Please note in some cases, one task depends on another and you have no choice but to wait. For example, if Delay1 was getting a file from a remote machine and Delay2 was getting another file, and Delay3 would process both files then Delay1 and Delay2 can be done "Concurrently" but Delay3 will need to wait for them to return before it can start.

And to avoid confusion, please note that in your case T1, T2 and T3 are only doing the sleep. The returning of hello will be done by the P1 thread. There are ways so that P1 does not have to do this but in your case control will be give to P1 once sleeping is done.

Upvotes: -2

Brett Caswell
Brett Caswell

Reputation: 1504

Here is a snippet for you to review. put it a console application to test it.

There are two workflows in this sample, the first mimics your pattern (with helpers). The other shows using await on a whenAll task and using the results in the assignment of a initated type (in this case a dynamic).


The underline concept of using await is just to ensure a task has completed before continuing in the scope of that function. the 'Async' keyword assures the scope of the function is going to be in a task (hence returning a task) and that there will be task execution that scope (hence the requirement of await).

if you do not intend to have a task complete before continuing, do not use async/await

namespace TaskAsyncTests
{
    using System.Threading.Tasks;

    class Program
    {
        static async Task<KeyValuePair<string, long>> TaskThis(Func<string> fn)
        {
            var watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            var task = Task.Run(fn); //fn will be 1sec
            await Task.Delay(1000);  //also being delayed 1sec here
            var result = await task;
            watch.Stop();
            return new KeyValuePair<string, long>(result, watch.ElapsedMilliseconds); //result should only be approx. 1 sec though
        }

        static void Main(string[] args)
        {
            var watch = new System.Diagnostics.Stopwatch();
            watch.Start();
            var results = Run(Task.WhenAll(new[]
            {
                TaskThis(LongProcessingFunction),
                TaskThis(LongProcessingFunction),
                TaskThis(LongProcessingFunction),
            }));
            watch.Stop();
            foreach (KeyValuePair<string, long> item in results)
            {
                Console.WriteLine(@"result:= '{0}' ElapsedMilliseconds := {1}", item.Key, item.Value.ToString());
            }
            Console.WriteLine("total ElapsedMilliseconds := {0}", watch.ElapsedMilliseconds);

            watch.Reset();
            watch.Start();
            var result = Run(GetSomethingAsync());
            watch.Stop();

            Console.WriteLine(@"result->PropertyOne := '{0}' ElapsedMilliseconds := {1}", result.PropertyOne.Key, result.PropertyOne.Value.ToString());
            Console.WriteLine(@"result->PropertyTwo := '{0}' ElapsedMilliseconds := {1}", result.PropertyTwo.Key, result.PropertyTwo.Value.ToString());
            Console.WriteLine(@"result->PropertyThree := '{0}' ElapsedMilliseconds := {1}", result.PropertyThree.Key, result.PropertyThree.Value.ToString());
            Console.WriteLine("total ElapsedMilliseconds := {0}", watch.ElapsedMilliseconds);

            Console.ReadLine();
        }

        static string LongProcessingFunction()
        {
            Task.Delay(1000).Wait();

            return "Hello World";
        }

        static T Run<T>(Task<T> taskRunner)
        {
            return taskRunner.Result;
        }

        static T[] Run<T>(Task<T[]> taskRunner)
        {
            return taskRunner.Result;
        }

        static async Task<dynamic> GetSomethingAsync()
        {
            var resultsTask = Task.WhenAll(new[] 
            {
                TaskThis(LongProcessingFunction),
                TaskThis(LongProcessingFunction),
                TaskThis(LongProcessingFunction)
            }).ConfigureAwait(false);

            // do other stuff here
            Task.Delay(2000).Wait();

            var results = await resultsTask;
            return new
            {
                PropertyOne = results[0],
                PropertyTwo = results[1],
                PropertyThree = results[2]
            };
        }
    }
}

my results were:

result:= 'Hello World' ElapsedMilliseconds := 1025
result:= 'Hello World' ElapsedMilliseconds := 1014
result:= 'Hello World' ElapsedMilliseconds := 1014
total ElapsedMilliseconds := 1028
result->PropertyOne := 'Hello World' ElapsedMilliseconds := 1001
result->PropertyTwo := 'Hello World' ElapsedMilliseconds := 1001
result->PropertyThree := 'Hello World' ElapsedMilliseconds := 1000
total ElapsedMilliseconds := 2001

Upvotes: 1

Mario Vernari
Mario Vernari

Reputation: 7306

The async/await syntax allows to write a more readable pattern than if it weren't. To make you understand the real meaning, consider your snippet rewritten as follows:

class Content
{

    public void Delay1(Action<string> callback)
    {
        //something which takes 5000ms
        callback("hello");
    }
    public void Delay2(Action<string> callback)
    {
        //something which takes 5000ms
        callback("hello");
    }
    public void Delay3(Action<string> callback)
    {
        //something which takes 5000ms
        callback("hello");
    }
    public void Print(Action callback)
    {
        this.Delay1(x=> {
            Console.WriteLine(x);
            this.Delay2(y => {
                Console.WriteLine(y);
                this.Delay3(z=> {
                    Console.WriteLine(z);
                    callback();
                }); 
            });
        });
    }
}

You should not relay on what the system actually does (in the case of Task.Delay, for instance). It could relay the "delay" to some other thread (queues for the ThreadPool), but it might execute the job in the same as the caller one.

As @Rob correctly stated, asynchrony is NOT concurrency: the code is executed in sequence.

It's just something to make you understand: nothing more.

Upvotes: 2

Aar&#243;nBC.
Aar&#243;nBC.

Reputation: 1330

When you're calling await, you're asking the curret method to stop execution until the "awaited" call ends; so, when you say:

Console.WriteLine(await Delay1());

You're asking for the current method (Print) to stop until Delay1() finishes.

In order to achieve the result you expected you must call them without the await instruction (it is not mandatory for an async method to be called with an await).

Here's an example:

    public class Content
    {

        public async void Delay1()
        {
            await Task.Delay(5000);
            Console.WriteLine("hello");
        }
        public async void Delay2()
        {
            await Task.Delay(5000);
            Console.WriteLine("hello");
        }
        public async void Delay3()
        {
            await Task.Delay(5000);
            Console.WriteLine("hello");
        }
        public void Print()
        {
            Delay1();
            Delay2();
            Delay3();
        }
    }

EDIT: As @Adrian pointed out, the Print method could finish sooner than the three Delay method calls. I didn't notice it in my test because of a Console.ReadLine();

Upvotes: 2

Related Questions