查找无休止的异步方法调用

在将ASP.NET应用程序迁移到async / await模型时,我偶然发现了一个相当危险的情况。

情况是我创建了一个async方法: async Task DoWhateverAsync() ,将接口中的声明更改为Task DoWhateverAsync()并希望编译器通过该警告告诉我代码现在在哪里出错。 好吧,运气不好。 只要通过接口注入该对象,就不会发出警告。 🙁

这很危险。 有没有办法自动检查返回任务的非等待方法? 我不介意一些警告太多,但我不想错过一个。

这是一个例子:

 using System.Threading.Tasks; namespace AsyncAwaitGames { // In my real case, that method just returns Task. public interface ICallee { Task DoSomethingAsync(); } public class Callee: ICallee { public async Task 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. } } } 

在遇到这个问题相当困难之后,我决定创建一个带有代码修复的Analyzer来解决它。

代码可在此处获取: https : //github.com/ykoksen/unused-task-warning

它也是一个NuGet包,可以用作项目的分析器(当它构建时): https : //www.nuget.org/packages/Lindhart.Analyser.MissingAwaitWarning/#

此外,它还可用作Visual Studio Extension(2017年)。 但是,这仅分析当前打开的文件,因此我建议使用NuGet包。 扩展可在此处获得(或在Visual Studio中搜索): https : //marketplace.visualstudio.com/items?itemName = Lindhart.missingAwaitWarning #overview

分析器的代码:

  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); } } } } } ///  /// Checks if the  is one of the types specified ///  ///  ///  ///  /// This method should probably be rewritten so it doesn't merely compare the names, but instead the actual type. private static bool EqualsType(ITypeSymbol typeSymbol, params Type[] type) { var fullSymbolNameWithoutGeneric = $"{typeSymbol.ContainingNamespace.ToDisplayString()}.{typeSymbol.Name}"; return type.Any(x => fullSymbolNameWithoutGeneric.Equals(x.FullName)); } 

编译器将发出警告CS4014但仅在调用方法为async才会发出警告 。

没有警告:

 Task CallingMethod() { DoWhateverAsync(); // More code that eventually returns a task. } 

警告CS4014: 由于未等待此呼叫,因此在呼叫完成之前,将继续执行当前方法。 考虑将’await’运算符应用于调用的结果。

 async Task CallingMethod() { DoWhateverAsync(); } 

这在您的特定情况下并不是非常有用,因为您必须找到调用DoWhateverAsync所有位置,并更改它们以获取警告然后修复代码。 但是您想首先使用编译器警告来查找这些调用。

我建议您使用Visual Studio查找DoWhateverAsync所有用法。 无论如何,您必须通过编译器警告或通过使用列表来修改周围的代码。

最后,我们使用roslyn查找忽略Task或Task <>返回值的所有实例:

 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); } 

你有几个选择:

  • 这是最简单的“穴居人”解决方案,在整个解决方案中使用内置VS搜索function(CTRL + SHIFT + F)搜索,也在查找选项下单击复选框使用正则表达式并使用此正则表达式: (?它假设您使用Async关键字发布了所有异步方法,并且方法调用在一行中 。如果不是如果为true,则不要使用它(或将缺少的validation添加到表达式中)。
  • 使用一些第三方代码分析工具,Nuget包。 ReSharper非常受欢迎,我相信它能够检测到这些问题,或者您可以创建自己的规则。
  • 我的选择是使用Roslyn( @Volker提供了一个解决方案 )。 您可以使用代码修复解决方案创建自己的规则集(灯泡图标将显示您的代码修复),这是最好的。

如何使用Roslyn:

  • 您必须从此处安装.NET Compiler Platform SDK
  • 使用VS 2017版本15.2(或更高版本)
  • 创建一个新项目File - > New - > Project,在Extensibility组下选择: Analyzer with Code Fix(Nuget + VSIX)您必须以.NET Framework 4.6.2为目标来创建此项目。 在此处输入图像描述

您可以复制粘贴以前的解决方案。 创建

 [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AsyncAwaitAnalyzer : DiagnosticAnalyzer { ... } 

用逻辑类来检测问题。 并创造

 [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AsyncAwaitCodeFixProvider)), Shared] public class AsyncAwaitCodeFixProvider : CodeFixProvider { ... } 

class为问题提供修复建议(添加等待)。

成功构建后,您将获得自己的.wsix程序包,您可以将其安装到VS实例,并且在VS重新启动后应该开始解决问题。

您可以在VS Project属性中添加一个特定警告,作为抛出编译错误的警告,如此处所述

您可以添加以分号分隔的警告代码列表,例如CS4014 ,如果您没有等待async方法,编译器将无法编译。

这是VS2017: VS2017配置的截图, 用于抛出警告CS4014的编译器错误