1

I've read a bunch of forums, tutorials and blogs talking about the usage of Async/Await in C#. The more I read the more confusing it gets. Also, people mainly talk about calling async stuff in a sync method but not calling sync stuff in an async method.

As I am a junior developer and do not have much experience with async programming I'll post a new question here and hope for some enlightenment.

Consider this:

I have a Web API endpoint that does some calculations and model building and returns some data.

public async Task<JsonResult> GetData()
    {
        Task<Data> stuff1Task = CalculateStuff1();
        Task<Data> stuff2Task = CalculateStuff2();
        Task<Data> stuff3Task = CalculateStuff3();

        return Json(
            new
            {
                stuff1 = await stuff1Task,
                stuff2 = await stuff2Task,
                stuff3 = await stuff3Task
            }, JsonRequestBehavior.AllowGet
        );
    }

    private async Task<Data> CalculateStuff1()
    {
        return await SomeAsyncCalculation();
    }

    private async Task<Data> CalculateStuff2()
    {
        return SomeSyncCalculation();
    }

    private async Task<Data> CalculateStuff3()
    {
        Task<Data> dataTask1 = SomeAsyncCalculation();
        Task<Data> dataTask2 = AnotherAsyncCalculation();
        
        Data data1 = await dataTask1;
        Data data2 = await dataTask2;

        Data combindedData = SyncMethodToCombineData(data1, data2);

        return combindedData;
    }

Why I consider mixing async and sync code is for getting better performance. In this case lets pretend SomeAsyncCalculation(), SomeSyncCalculation() and AnotherAsyncCalculation() are pretty costly methods. My goal is to get the methods to run somewhat in parallel to gain some faster response times.

I know it is best to go "Async all the way" but lets be real, rebuilding half the project is not always a priority or a possibility. Also I might have some integrations with other systems that do not support async operations.

This warning I get for CalculateStuff2() adds to the confusion. :

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

In my understanding the "async" keyword is only good for wrapping the method and allowing me to use await keyword. It also allows me to just return the data and I don't need to manage Task returning results. It also handles exceptions. The Task<TResult> return type is what makes the method execute on a different thread (although it is not guaranteed it will execute on a different thread).

Concluding questions:

1. Will the async method that does not use await (CalculateStuff2()) run synchronously on it's own thread (if it runs on another thread because it is a Task) or will it run in the main thread of the API call, and always block it no matter what?

2. Is it bad practice to use async without await just to have a nicely wrapped task method out of the box?

Danny Boy
  • 355
  • 3
  • 10
  • 1
    [Async/Await - Best Practices in Asynchronous Programming](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming) – Pavel Anikhouski Jul 28 '20 at 11:52
  • 4
    It will run synchronously on the same thread the original method ran on, there's no new threads automagically introduced by using the `async` keyword. Let me rephrase, that method is 99% identical to a method where you remove all the async bits, only difference is that a more complex state machine has been created around it, but that's it. – Lasse V. Karlsen Jul 28 '20 at 11:53
  • So in other words, this line, `var stuff2Task = CalculateStuff2();` will block until `CalculateStuff2` has returned, and completed. – Lasse V. Karlsen Jul 28 '20 at 11:53
  • 4
    1) No. Async is not about threads, and even async methods that do use await [like to stay on the same thread](https://stackoverflow.com/q/17661428/11683). *Asynchronously* has nothing to do with *parallel*. 2) Yes it is. If you don't use `await` inside the method, then don't mark it as `async`. `async` is not a part of the method signature. It's not what makes *it* awaitable. The fact that it returns a `Task` does. `private Task CalculateStuff2()` is perfectly fine and can be awaited by the caller. – GSerg Jul 28 '20 at 11:55
  • 1
    If you want to run a synchronous method, on a separate thread, you should use `var stuff2Task = Task.Run(CalculateStuff2);` and remove all async bits/keywords from `CalculateStuff2`. – Lasse V. Karlsen Jul 28 '20 at 11:56
  • 1
    In order for `CalculateStuff2` to compile at all, `SomeSyncCalculation` would need to return a `Task`. Which implies that these methods are very much falsely advertising what they're doing. Synchronous operations should be synchronous, and asynchronous operations should be asynchronous. It sounds like you may be confusing yourself by trying to make every method "potentially async", which is a bad idea. Async is not a golden hammer, and every method is not a nail. Heed the compiler warning and at least remove `async` from methods which don't `await` anything. – David Jul 28 '20 at 12:10
  • 1
    *"The more I read the more confusing it gets."* <=== We have all passed this stage. :-) Eventually you will realize that using async/await in its simplest form, is almost always the best way to use it. This technology was invented in order to make asynchronous programming simple. – Theodor Zoulias Jul 28 '20 at 12:12
  • Hey @David, thanks for you comment. Actually the code will compile just fine because of the `async` keyword. If I remove it though there is going to be a problem with the return type of `SomeSyncCalculation`. That also the cause of my question nr 2. Using the async keyword gives you a lot out of the box even if you don't need to use `await`. – Danny Boy Jul 28 '20 at 13:06
  • @DannyBoy: *"the code will compile just fine because of the async keyword"* - The compiler doesn't produce an error, but it does produce a warning. Warnings are not "just fine", they are the compiler's way of warning you of a potential problem. Best not to ignore them. *"If I remove it though there is going to be a problem with the return type"* - Yes, that's the compiler error I was referring to. Change the return type to `Data` and make it synchronous, there's no reason to "pretend" to be asynchronous. *"the async keyword gives you a lot out of the box"* - Only for actual async methods. – David Jul 28 '20 at 13:19
  • @David thanks for the clarification. So in general I should think that - If there is nothing to await, the method is not asynchronous? – Danny Boy Jul 28 '20 at 13:31
  • 1
    @DannyBoy: Correct. (The answer below likely articulated it better than I did.) Keep synchronous and asynchronous methods clear about which of the two they are. A method which awaits no async operations shouldn't advertise that it does, as consuming code may not expect the method to be a long-running blocking operation. And a method which does have async operations *definitely* shouldn't pretend to be synchronous, because it invalidates the ability to be awaited by calling code. – David Jul 28 '20 at 13:36
  • 2
    As a note the purpose of async is not to speed-up any single operation but to prevent threads from blocking (for example IO). Async just enables the threads to do different stuff while waiting for something to be finished. If you want to run something parallel use methods like [`AsParallel`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.parallelenumerable.asparallel?view=netcore-3.1) and [`Parallel.ForEach`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreach?view=netcore-3.1) – Ackdari Jul 28 '20 at 14:04
  • 1
    **Strange moderation:** The question in general was about how properly to launch some CPU-Bound operation asynchronously with optimization, exactly "is `async` method suitable for wrapping sync code?". Marked as duplicate of State Machine optimization for `return await`. Am I the only who suprised? I will not flag the question to remove the dup. I don't like moderators here at all (because they are too agressive, as for me). And I have no enough reputaion to request reopening. :( – aepot Jul 28 '20 at 14:45
  • Thanks for your comment @aepot. I really feel like this is not a duplicate and I don't seem to be able to do something about it. I changed the title of the question and I hope it will be re-opened. – Danny Boy Jul 29 '20 at 16:19

1 Answers1

3

You're not need for async in sync method. async generates State Machine that is a kind of redundancy in case you're not need for await.

Consider this somewhat optimized example.

public async Task<JsonResult> GetData()
{
    Task<Data> stuff1Task = CalculateStuff1();
    Task<Data> stuff3Task = CalculateStuff3();
    Data stuff2data = CalculateStuff2(); // run sync method after launching async ones

    return Json(new
           {
               stuff1 = await stuff1Task,
               stuff2 = stuff2data,
               stuff3 = await stuff3Task
           }, JsonRequestBehavior.AllowGet);
}

private Task<Data> CalculateStuff1() // optimized
{
    return SomeAsyncCalculation();
}

private Data CalculateStuff2()
{
    return SomeSyncCalculation();
}

private async Task<Data> CalculateStuff3()
{
    //use combinator to simplify the code
    Data[] data = await Task.WhenAll(SomeAsyncCalculation(), AnotherAsyncCalculation());

    Data combindedData = SyncMethodToCombineData(data[0], data[1]);

    return combindedData;
}

Also consider to differ the CPU-bound and IO-bound operations, look at this article. There's different async approach depending on what exacly you're launching.

Direct answers

  1. Will the async method that does not use await (CalculateStuff2()) run synchronously on it's own thread (if it runs on another thread because it is a Task) or will it run in the main thread of the API call, and always block it no matter what?

Yes, it will run synchronously on the caller Thread. If you want to run some sync method on its own Thread, use Task.Run():

Task<Data> stuff2Task = Task.Run(() => CalculateStuff2());

and then await it.

  1. Is it bad practice to use async without await just to have a nicely wrapped task method out of the box?

Yes, it's bad practice. Redundant State Machine makes overhead which in this case is worthless.

aepot
  • 4,558
  • 2
  • 12
  • 24
  • 1
    Thank you for your answer! I removed var and changed it to the actual datatypes as you requested. Didn't think about that. So I should not use `Task` to call the sync method at all? All three `CalculateStuff` methods represent a bunch of I/O and CPU bound operations. – Danny Boy Jul 28 '20 at 13:14
  • @DannyBoy removed a avoid-var suggestion from the answer. – aepot Jul 28 '20 at 13:16
  • Also, as my second question suggests: Is not the state machine good to have even if I don't use `await` because it manages my return of data and exceptions thrown in the `Task` method etc? – Danny Boy Jul 28 '20 at 13:20
  • 1
    @DannyBoy Exceptions handling problem may appear if you're not awaiting some `async` operation at all, it's another long and not less interesting story: lookup some articles or blog posts about "avoid async void" (very popular topic), it's exactly about the exceptions handling in async code. – aepot Jul 28 '20 at 13:25