Reputation: 1817
I've just stumbled across a rather dangerous scenario while migrating an ASP.NET application to the async/await model.
The situation is that I made a method async: async Task DoWhateverAsync()
, changed the declaration in the interface to Task DoWhateverAsync()
and hoped that the compiler would tell me where the code now is wrong, via that Warning. Well, tough luck. Wherever that object gets injected via the interface, the warning doesn't happen. :-(
This is dangerous. Is there any way to check automatically for non-awaited methods that return tasks? I don't mind a few warnings too many, but I wouldn't want to miss one.
Here's an example:
using System.Threading.Tasks;
namespace AsyncAwaitGames
{
// In my real case, that method just returns Task.
public interface ICallee { Task<int> DoSomethingAsync(); }
public class Callee: ICallee
{
public async Task<int> DoSomethingAsync() => await Task.FromResult(0);
}
public class Caller
{
public void DoCall()
{
ICallee xxx = new Callee();
// In my real case, the method just returns Task,
// so there is no type mismatch when assigning a result
// either.
xxx.DoSomethingAsync(); // This is where I had hoped for a warning.
}
}
}
Upvotes: 54
Views: 12738
Reputation: 5322
In addition to the other answers, readers should consider adding the Microsoft.VisualStudio.Threading.Analyzers
package as it contains a number of useful rules including:
And the default analyzers contain rules suggesting to use async alternatives when offered like CA1849. The catch is that it's not enabled by default, so make sure the analysis mode is set to All as well as analyzers are enabled if your project is targeting < net5
Upvotes: 1
Reputation: 323
I found a great Visual Studio extension/NuGet package for this. Saves you having to create your own like other answers suggest.
https://github.com/ykoksen/unused-task-warning
Once I installed the Visual Studio extension I went to Analyze/Run Code Analysis/On Solution and it found several places where I had this issue.
Upvotes: 0
Reputation: 6658
You have a few options:
(?<!await|task(.*))\s([_a-zA-Z0-9\.])*Async\(
It assumes you post fixed all your async method with the Async keyword and the method call is in one line. If it is not true then do not use it (or add the missing validations to the expression).How to use Roslyn:
You can copy paste the previous solution. Create
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AsyncAwaitAnalyzer : DiagnosticAnalyzer
{ ...
}
class with logic, to detect the issue. And create
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AsyncAwaitCodeFixProvider)), Shared]
public class AsyncAwaitCodeFixProvider : CodeFixProvider
{ ...
}
class to provide fixing suggestions (add await) to the problem.
After a success build you will get your own .wsix package you can install it to your VS instance and after a VS restart is should start pick up the problems.
Upvotes: 13
Reputation: 1405
After quite some difficulties with this problem I decided to create an Analyzer with code fix to solve it.
The code is available here: https://github.com/ykoksen/unused-task-warning
It is also as a NuGet package that can be used as an analyzer for a project (when it is build): https://www.nuget.org/packages/Lindhart.Analyser.MissingAwaitWarning/#
Furthermore it is also available as a Visual Studio Extension (for 2017). However this only analyses currently open files, so I'd recommend using the NuGet package. The extension is available here (or search for it in Visual Studio): https://marketplace.visualstudio.com/items?itemName=Lindhart.missingAwaitWarning#overview
The code for the analyzer:
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyseSymbolNode, SyntaxKind.InvocationExpression);
}
private void AnalyseSymbolNode(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
if (syntaxNodeAnalysisContext.Node is InvocationExpressionSyntax node)
{
if (syntaxNodeAnalysisContext
.SemanticModel
.GetSymbolInfo(node.Expression, syntaxNodeAnalysisContext.CancellationToken)
.Symbol is IMethodSymbol methodSymbol)
{
if (node.Parent is ExpressionStatementSyntax)
{
// Only checks for the two most common awaitable types. In principle this should instead check all types that are awaitable
if (EqualsType(methodSymbol.ReturnType, typeof(Task), typeof(ConfiguredTaskAwaitable)))
{
var diagnostic = Diagnostic.Create(Rule, node.GetLocation(), methodSymbol.ToDisplayString());
syntaxNodeAnalysisContext.ReportDiagnostic(diagnostic);
}
}
}
}
}
/// <summary>
/// Checks if the <paramref name="typeSymbol"/> is one of the types specified
/// </summary>
/// <param name="typeSymbol"></param>
/// <param name="type"></param>
/// <returns></returns>
/// <remarks>This method should probably be rewritten so it doesn't merely compare the names, but instead the actual type.</remarks>
private static bool EqualsType(ITypeSymbol typeSymbol, params Type[] type)
{
var fullSymbolNameWithoutGeneric = $"{typeSymbol.ContainingNamespace.ToDisplayString()}.{typeSymbol.Name}";
return type.Any(x => fullSymbolNameWithoutGeneric.Equals(x.FullName));
}
Upvotes: 33
Reputation: 69
You can add a specific warning in VS Project properties as one that throws a compilation error, like described here
You can add a semi-colon separated list of warning codes e.g. CS4014
, and the compiler will fail to compile if you're not awaiting an async
method.
Here's a screenshot with VS2017: VS2017 configuration to throw compiler errors for warning CS4014
Upvotes: 1
Reputation: 1817
In the end, we used roslyn to find all instances where a return value of Task or Task<> was ignored:
if (methodSymbol.ReturnType.Equals(syntaxNodeAnalysisContext.SemanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName)))
{
// For all such symbols, produce a diagnostic.
var diagnostic = Diagnostic.Create(Rule, node.GetLocation(), methodSymbol.ToDisplayString());
syntaxNodeAnalysisContext.ReportDiagnostic(diagnostic);
}
if (((INamedTypeSymbol) methodSymbol.ReturnType).IsGenericType && ((INamedTypeSymbol) methodSymbol.ReturnType).BaseType.Equals(syntaxNodeAnalysisContext.SemanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName)))
{
// For all such symbols, produce a diagnostic.
var diagnostic = Diagnostic.Create(Rule, node.GetLocation(), methodSymbol.ToDisplayString());
syntaxNodeAnalysisContext.ReportDiagnostic(diagnostic);
}
Upvotes: 8
Reputation: 106826
The compiler will emit warning CS4014 but it is only emitted if the calling method is async
.
No warning:
Task CallingMethod() {
DoWhateverAsync();
// More code that eventually returns a task.
}
Warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
async Task CallingMethod() {
DoWhateverAsync();
}
This is not terrible useful in your specific case because you have to find all the places where DoWhateverAsync
is called and change them to get the warning and then fix the code. But you wanted to use the compiler warning to find these calls in the first place.
I suggest that you use Visual Studio to find all usages of DoWhateverAsync
. You will have to modify the surrounding code anyway either by going through compiler warnings or by working through a list of usages.
Upvotes: 7