Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler warning CS4014 not emitted during build

Compiler warning CS4014 (calling async method without awaiting result) is not emitted as a warning during build when the called method is in a referenced assembly.

When the called method is in the same assembly the warning is correctly emitted.

The compiler warning is signaled in Visual Studio when both projects are contained in the same solution.

The difference seems to be caused by the compiler having only the compiled referenced assembly and Visual Studio having the source code to both assemblies.

The question is: why have these two different behaviors? And is there any way to have the CS4014 warning emitted during compilation?

To replicate this behavior setup two class libraries, both having one code file:

TestClassLibrary1

public class Class1
{
    public static async Task<string> DoSomething()
    {
        return await Task.FromResult("test");
    }
}

TestClassLibrary2 (referencing TestClassLibrary1)

public class Class2
{
    public void CallingDoSomething()
    {
        Class1.DoSomething();
    }
}

Compiling these projects will complete without warnings. Opening them in the same solution in Visual Studio will result in 1 error being shown in the Error List and a red squiggly line under Class1.DoSomething().

like image 696
Benjamin Wegman Avatar asked Oct 05 '16 09:10

Benjamin Wegman


1 Answers

The async modifier allows you to write code that returns a Task more conveniently (with await), but it has no representation in IL*. In a compiled assembly, the method simply looks like public static Task<string> DoSomething to the compiler, and calling those without awaiting their result doesn't trigger the warning (even if the methods live in the same assembly). Replace Class1.DoSomething with something else that returns a task and ought to be awaited (say Task.Delay(2000)) and you'll likewise see the compiler doesn't warn. When all of the source code is available, however, the compiler (and by compiler I mean Roslyn) can identify the method as async because the modifier is still part of the syntax tree.

So why doesn't the compiler just always warn when you call a method returning a Task without using the result, regardless of whether it happened to be written using async? Good question. While there are plenty of legitimate scenarios where you don't wait to await a Task (because you want to pass it to Task.WhenAll for example) all of these involve storing the Task somewhere else, which would not raise the warning. Calling a method that returns a Task and discarding the result entirely is almost certainly a mistake (and when it's intentional, there are elegant ways of suppressing the warning), which is why this warning exists in the first place.

I suspect the implementation of this warning could use a tweak (or a replacement with a new warning), but only the people working on the compiler would know that for sure.


*: this isn't actually true; async methods have an AsyncStateMachineAttribute applied to them for more convenient debugging. But, for whatever reason, the compiler doesn't use this to identify async methods across assemblies. Nor should it, arguably: there's nothing particularly special about async in terms of the Task the method returns. But if they wanted to preserve the stated semantics of CS4104 exactly (warn if the result of an async method is unused) this would be one way to do it.

like image 128
Jeroen Mostert Avatar answered Nov 03 '22 19:11

Jeroen Mostert