0

I am trying to figure it out that why these two Async method not working parallel. Note that each method returns some html, and for this Iteration inside it (its demo code) (original code is in my .Net project).

Each method individually takes 9 seconds to complete.

Both method are Async even it takes 18 seconds to complete if its invoke and run in single method. It means it not runs parallel.

Overall what i want is, It should take 9 second to complete both the method. How it is possible? Please suggest to run both method parallel. (I want this because in my project there are many method inside one method, i want all runs parallel)

Thanks in advance.

Please review below code:

namespace LearnAynchStackOverflow
{
class Program
{
    static async Task Main(string[] args)
    {
        var startTime = DateTime.Now;
        Console.WriteLine($"Start time: {startTime}");

        var emailHtml = EmailTemplate.EmailHtml();
        var smsHtml = SmsTemplate.SmsHtml();

        var f = await Task.WhenAll(emailHtml, smsHtml);

        var endTime = DateTime.Now;
        Console.WriteLine($"End time: {endTime}");

        Console.ReadLine();
    }
}

public class EmailTemplate
{
    //This method takes 9 seconds to complete
    public static async Task<string> EmailHtml()
    {
        string str = string.Empty;
        for (int i = 0; i < 25000; i++)
        {
            str += "html1" + i + " ";
            str += "html1" + i + " ";
        }
        var a = await Task.FromResult(str);
        return a;
    }
}

public class SmsTemplate
{
    //This method takes 9 seconds to complete
    public static async Task<string> SmsHtml()
    {
        string str = string.Empty;
        for (int i = 0; i < 25000; i++)
        {
            str += "html2" + i + " ";
            str += "html2" + i + " ";
        }
        var z = await Task.FromResult(str);
        return z;
    }
}
}

And Below is my console result which takes 18 seconds:

enter image description here

ThermeshB
  • 61
  • 1
  • 11
  • Most likely the server is seeing more than one connection from the same client IP and only processing one at a time. Check with a sniffer like wireshark or fiddler to see if the two mail messages are being sent in parallel. – jdweng Dec 25 '19 at 14:01
  • @jdweng Its not related to server or IP address, please suggest right answer. Also its regarding to run both method parallel, its not regarding to send Email. – ThermeshB Dec 25 '19 at 14:04
  • You can use Parallel.Invoke for parallel execution. – Saeid Babaei Dec 25 '19 at 14:13
  • 1
    Using async (nor await) keyword doesn't automatically make the method run asynchronously. For CPU-bound tasks, consider using Task.Run method or System.Threading.Tasks.Parallel methods. See also [async/await FAQ](https://devblogs.microsoft.com/pfxteam/asyncawait-faq/) – tukaef Dec 25 '19 at 14:40
  • How can you tell that is not the server? Suppose the two messages were sent at 5:11:10 PM. And the server processed the first message immediately and finished the first at 5:11:17. Then server started second message at 5:11:18 and finished second message at 5:11:24. – jdweng Dec 25 '19 at 14:45
  • 1
    Does this answer your question? [Async method not running in parallel](https://stackoverflow.com/questions/32318860/async-method-not-running-in-parallel) – tukaef Dec 25 '19 at 14:54
  • @@tukaef , thanks but it is not working for me – ThermeshB Dec 25 '19 at 15:06

4 Answers4

5

Your methods are asynchronous only on paper. They both always complete synchronously, because await Task.FromResult(str) completes immediately without any asynchronous operations. Both calls:

var emailHtml = EmailTemplate.EmailHtml();
var smsHtml = SmsTemplate.SmsHtml();

run the loop synchronously and return a completed task. Then the Task.WhenAll also returns a completed task. There is never any asynchrony, no continuations, only plain old sequential execution.

Making your method async doesn't magically cause it to be ran on a separate thread, actually, There Is No Thread. Your task has nothing to do with asynchronous operations. If you want to run CPU-bound tasks on the thread pool, use Task.Run

public class EmailTemplate
{
    public static string EmailHtml()
    {
        string str = string.Empty;
        for (int i = 0; i < 25000; i++)
        {
            str += "html1" + i + " ";
            str += "html1" + i + " ";
        }
        return str;
    }
}

public class SmsTemplate
{
    public static string SmsHtml()
    {
        string str = string.Empty;
        for (int i = 0; i < 25000; i++)
        {
            str += "html2" + i + " ";
            str += "html2" + i + " ";
        }
        return str;
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        var startTime = DateTime.Now;
        Console.WriteLine($"Start time: {startTime}");

        var emailHtml = Task.Run(() => EmailTemplate.EmailHtml());
        var smsHtml = Task.Run(() => SmsTemplate.SmsHtml());

        var f = await Task.WhenAll(emailHtml, smsHtml);

        var endTime = DateTime.Now;
        Console.WriteLine($"End time: {endTime}");

        Console.ReadLine();
    }
}

As an aside, don't you think that concatenating a string 25000 times taking 9 seconds is a bit pathological? I do, that's why I'd use a StringBuilder.

public static string EmailHtml()
{
    var stringBuilder = new StringBuilder();
    for (int i = 0; i < 25000; i++)
    {
        stringBuilder.Append("html1").Append(i).Append(" ");
        // I don't know if this duplication is intentional, but I left it in case it was.
        stringBuilder.Append("html1").Append(i).Append(" ");
    }
    return stringBuilder.ToString();
}
V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • @@V0Idek - thanks friend, but only little bit improvement on time consuming. I run your suggested code thrice, it takes 15 seconds now. (3 seconds improvement). My question is that - both the method is taking 9 seconds and also i run it synchronously then why it takes 18 seconds, instead of 9 seconds? It should take 9 seconds overall instead of 9 seconds for each(9+9). [Also thanks for Suggest to use StringBuilder, i know that it will gives you results in millisecond, but here i just use string intensionally] – ThermeshB Dec 25 '19 at 15:48
  • 1
    @ThermeshB I think you misunderstand the word "synchronously". It means that the CPU is occupied with calculating the result the entire time. Computing a result _asynchronously_ would mean (moreless) that it's prepared "on the side" without using the CPU, it gets completed when the CPU is already doing something else (asynchronous completion) and then gets picked up and a continuation fires using that result. In this case "synchronously" means the same as "sequentially". The methods are executed one after the other and not in parallel. – V0ldek Dec 25 '19 at 16:07
  • 2
    @ThermeshB I suggest you read on differences between parallelism and asynchrony. There's certainly much more to unpack that anyone could ever put into an SO answer, but you can start with [this question](https://stackoverflow.com/questions/4844637/what-is-the-difference-between-concurrency-parallelism-and-asynchronous-methods) – V0ldek Dec 25 '19 at 16:08
  • @@V0Idek - OMG friend, i got my answer now, it really consumes my half of the day. Its just .Net version issue. The same code takes 18 seconds on .net version 4.5. But if i run the same code in .Net core 3.0 it runs synchronously takes 9 seconds, Wow. This will help to many developers to compare .Net and .Net Core. I am adding my own answer with screenshot. Realy thanks for your help. – ThermeshB Dec 25 '19 at 16:27
  • 2
    @ThermeshB All of my tests were done on .NET Core 3.1, the framework version doesn't change whether your code runs concurrently or sequentially. – V0ldek Dec 25 '19 at 17:13
2

Your sample code runs synchronously. To understand why, you need to understand how asynchronous methods work.

All asynchronous methods start running synchronously - just like any other method. The magic happens at the first await that acts on an incomplete Task. At that point, the method returns its own incomplete Task that the calling method can use to know when the task has actually finished.

Your sample code never returns an incomplete Task. Task.FromResult returns a completed Task. When await sees a completed Task, your method continues running synchronously, just like any other method.

So when you do this:

var emailHtml = EmailTemplate.EmailHtml();
var smsHtml = SmsTemplate.SmsHtml();

Your call to SmsHtml() doesn't even start until EmailHtml() has fully completed.

If you are using await Task.FromResult to try to simulate some asynchronous work (like actually sending an SMS or email), then that won't work. You can use Task.Delay() instead. That will return an incomplete Task that will resolve when the timer runs out.

See if you get different results using this code:

public class EmailTemplate
{
    //This method takes 9 seconds to complete
    public static async Task<string> EmailHtml()
    {
        string str = string.Empty;
        for (int i = 0; i < 25000; i++)
        {
            str += "html1" + i + " ";
            str += "html1" + i + " ";
        }
        await Task.Delay(9000); //wait 9 seconds asynchronously
        return str;
    }
}

public class SmsTemplate
{
    //This method takes 9 seconds to complete
    public static async Task<string> SmsHtml()
    {
        string str = string.Empty;
        for (int i = 0; i < 25000; i++)
        {
            str += "html2" + i + " ";
            str += "html2" + i + " ";
        }
        await Task.Delay(9000); //wait 9 seconds asynchronously
        return str;
    }
}

Your code in Main is correct for what you're trying to do. But just note that asynchronous does not mean parallel:

  • Parallel means two lines of code are being evaluated simultaneously (on different threads)
  • Asynchronous means releasing the current thread while you wait for some external thing to happen. This allows you to start some other operation while you wait.

Parallel is about how your code runs. Asynchronous is about how your code waits.

In the changed code I gave, the initial parts of each method will never run in parallel. EmailHtml() will run up until the await, when it returns a Task. Then SmsHtml() will start running, up until the await. That will happen one after the other.

The continuations of those methods (everything after await) might run in parallel (on different threads) depending on the type of application.

Microsoft has a series of well-written articles about Asynchronous programming with async and await that are worth reading. It should help you understand better how all this works.

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
0

result I have just copied and run your code and it completes in 7 seconds. It seems to be correct. Your results may differ according to many reasons. What kind of app are you running (ASP.NET, Console)? What your PC configuration is? What version of .NET are you using?

Viacheslav
  • 120
  • 2
  • 11
  • Many thanks, Well i am using ASP.NET Console, .Net version 4.5 – ThermeshB Dec 25 '19 at 14:44
  • @ThermeshB Hi! What do you mean by ASP.NET Console? What type of project did you choose when you created it? – Viacheslav Dec 25 '19 at 14:50
  • Hi, its ASP.NET mvc project, and for this code i used Asp.Net console, .Net version 4.5. (Console App, .net framework C#) – ThermeshB Dec 25 '19 at 14:54
  • @ThermeshB Then consider [this answer](https://stackoverflow.com/questions/52161832/how-to-make-asp-net-core-web-api-action-execute-asynchronously) – Viacheslav Dec 25 '19 at 14:58
  • @@Viacheslav - i read the whole answer of your suggested link, its not working friend. Can you please make Asp.Net console app in .Net version 4.5 again. and please suggest some idea? – ThermeshB Dec 25 '19 at 15:09
  • There is no such thing as "ASP.NET Console". Did you mean ASP.NET Core? – Gabriel Luci Dec 25 '19 at 15:12
  • @@Gabriel - No, its console app (.Net framework) VS 2019 > create project > Console App (.Net framework) > .net version 4.5 – ThermeshB Dec 25 '19 at 15:19
  • I see. That is just referred to as a "console app". "ASP.NET" is a web app – Gabriel Luci Dec 25 '19 at 15:29
  • @ThermeshB alright, I get it. See, when you create a method and mark it as `async` there is no magic that makes it asynchronous. It **will** be executed synchronously till the first `await` keyword. Now, in your code you use the `await` keyword after the loops (which, obviously take most CPU time). You can either add an awaited call of `Task.Yield()` method in the first line of your methods or make both of your methods synchronous and runthem with `Task.Factory.StartNew(Func func, TaskCreationOptions options)` method, specifying `TaskCreationOptions.LongRunning` as a second parameter. – Viacheslav Dec 25 '19 at 15:37
  • @@Viacheslav - OMG friend, i got my answer now, it really consumes my half of the day. Its just .Net version issue. The same code takes 18 seconds on .net version 4.5. But if i run the same code in .Net core 3.0 it runs synchronously takes 9 seconds, Wow. This will help to many developers to compare .Net and .Net Core. I am adding my own answer with screenshot. Realy thanks for your help. – ThermeshB Dec 25 '19 at 16:28
  • @ThermeshB that only means that .NET Core runs faster and does not solves your issue. Measure your both methods separately to ensure. – Viacheslav Dec 25 '19 at 16:30
  • @@Viacheslav - ok i will, You are right friend - when i run method seperately - it takes only 4 seconds. It also means it doesnt resolve my issue. – ThermeshB Dec 25 '19 at 16:38
-3

Async keyword makes the Task inside the block run in parallel and all other sync blocks run sequentially. Making the method async will never make the code async.

What does async keyword do?

public static async Task<string> EmailHtml()
    {
        stmt1;
        (task)stmt2;
        stmt3;
        (task)stmt4;
    }

Here, EmailHtml() starts running sequentially, when it encounters a task (stmt2, stmt4) a new thread is taken from thread pool and will run in background (will never be merged to the main thread until it is awaited) and other stmts (1 & 3) will be sequential.

How to make the code run in parallel?
Place the code inside SmsHtml and EmailHtml inside Task.Run or Task.Factory.StartNew (will explicitly ask to run the block inside a new thread) according to your use case. Now, these two methods run in a different thread and the rest of your code will make it run in 9 seconds.

public static async Task<string> EmailHtml()
{
    return Task.Run(() => {return str;});
}


Note: All the tasks mentioned here are assumed to be running aync

Vignesh Prasad V
  • 419
  • 3
  • 17
  • 2
    Your description is inaccurate to the point of being erroneous. "Async keyword makes the Task inside the block run in parallel" - no, `Task`s inside an `async` method may still complete synchronously. "[A] new thread is taken from thread pool and will run in background" - no, [There Is No Thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html). You are right to suggest `Task.Run`, but when suggesting `Task.Factory.StartNew` you should probably mention [the risks that come with it](https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html). – V0ldek Dec 25 '19 at 15:50
  • A proper task should be running async, and this ans is based on that! need not mention explicitly! – Vignesh Prasad V Dec 25 '19 at 16:02
  • Not necessarily, it's common in many use cases that the result is ready for consumption when the asynchronous method returns. We got an entirely new piece of async machinery in form of a `ValueTask` in .NET Core 2.0 to facilitate that! The compiler even optimises for the case when the operation completes synchronously, doesn't schedule continuations, caches completed Tasks, etc. My point is that you're oversimplifying a vastly complicated topic and introducing misconceptions by doing that. Concurrency is the last thing you want devs to have misconceptions about, given how complicated it is. – V0ldek Dec 25 '19 at 16:14
  • 1
    I agree with @V0ldek. This is a very inaccurate answer. Please read Microsoft's documentation on [Asynchronous programming with async and await](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/) (it is a whole series of articles, which you can see on the left of that page) – Gabriel Luci Dec 25 '19 at 16:44