2

I've come across some code that is marked as "async" but doesn't make any asynchronous calls. A dumbed-down version:

public async Task<int> NotReallyAsynchronous() {
    // Some code... var someInt = ...;
    return someInt;
}

I know this can lead to a chain of awaits to appear throughout the codebase, but other than that, what are the dangers of it?

It results in a compiler warning, and I intend to change the code so it no longer has the async modifier, but I am interested in knowing if there are other reasons that it is problematic.

Macho Matt
  • 1,286
  • 4
  • 16
  • 32
  • 2
    `I know that this sends a signal to the caller that the method is asynchronous` - no, it doesn't. `async` is not a part of the method signature. What sends the signal that the method is asynchronous is the `Task`. You can keep the `async` and the caller may opt to not `await` you. You can remove the `async` and the caller may still `await` you. You add the `async` keyword to *your* method when *you* need to use `await` inside that method. If you don't, you don't add the `async` keyword. – GSerg Oct 19 '21 at 18:57
  • @GSerg Microsoft may want to change the very first line of its documentation then. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/async. "signal" may have the been wrong choice of words; "implies" may have been better. – Macho Matt Oct 19 '21 at 19:01
  • 1
    The page you linked to is not language-lawyer reference material, so it is rather casual with the terminology. – Raymond Chen Oct 19 '21 at 19:05
  • 3
    By "asynchronous" they probably meant "[compiled into a state machine](https://stackoverflow.com/a/56587815/11683)", which is then correct. In your question by "asynchronous" you probably meant "[causing a chain of awaits to appear through the codebase](https://stackoverflow.com/a/14189020/11683)", which is also okay as a meaning, but a more precise term would probably be [awaitable](https://stackoverflow.com/q/12661348/11683). – GSerg Oct 19 '21 at 19:11
  • 2
    For completeness, [this](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/classes#async-functions) *is* the language lawyer reference material. Note how the word "asynchronous" is never used; that's because describing the semantics of `async` doesn't actually require asynchrony, even though that is the intended application. – Jeroen Mostert Oct 19 '21 at 19:16
  • @GSerg In that link it uses asynchronous to mean it's actual definition, which is an operation that returns before it has finished and that will complete at some later point in time, which is also how the OP is using it. Neither of the definitions you've mentioned make any sense given how any of the things you're referencing use the word. – Servy Oct 19 '21 at 19:47
  • If you have a performance query, you should use benchmarkdotnet and process that query for your self. Programming languages are complicated, there are many levels of optimization, a lot of things aren't obvious. Talking about what might and might not happen becomes less useful than talking about what it means to my project – TheGeneral Oct 19 '21 at 20:22

1 Answers1

3

One reason is that the generated IL (intermediate language) code is more costly when a function is declared async. The C# async/await feature is not a feature that's available at the IL level, so the compiler has to generate some extra code to make that feature work in IL. This could result in less efficient performance (both in speed and memory usage) at runtime.

For example, compare the two methods in this SharpLab demo.

public async Task<int> NotReallyAsynchronous(){
    return 5;
}

public int Sync(){
    return 7;
}

In IL, Sync is just a single method, short enough to copy here:

.method public hidebysig 
        instance int32 Sync () cil managed 
    {
        // Method begins at RVA 0x208b
        // Code size 2 (0x2)
        .maxstack 8

        IL_0000: ldc.i4.7
        IL_0001: ret
    } // end of method C::Sync

Whereas NotReallyAsynchronous has a method, but also a nested type <NotReallyAsynchronous>d__0 containing two fields and two methods, the latter of which are not trivial and too large for me to paste here. Maybe not a huge amount of code generally speaking, but clearly if you can reduce it (across a large number of cases) by omitting async, it could have an impact.

Joe Sewell
  • 6,067
  • 1
  • 21
  • 34
  • 2
    Compare `public Task TaskWithoutAsync() { return Task.FromResult(42); }` which is what an optimizing compiler would do with a method marked `async` that never does any `await`. – Ben Voigt Oct 19 '21 at 20:15