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

时间:2017-01-18 13:55:42

标签: c# async-await

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

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

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

以下是一个例子:

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.
        }
    }
}

6 个答案:

答案 0 :(得分:10)

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

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

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

此外,它还可用作Visual Studio扩展(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);
                    }
                }
            }
        }
    }

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

答案 1 :(得分:6)

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

没有警告:

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

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

async Task CallingMethod() {
    DoWhateverAsync();
}

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

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

答案 2 :(得分:6)

最后,我们使用roslyn查找返回值为Task或Task&lt;&gt;的所有实例。被忽略了:

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

答案 3 :(得分:5)

您有几个选择:

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

如何使用Roslyn:

  • 您必须从here
  • 安装.NET Compiler Platform SDK
  • 使用VS 2017版本15.2(或更高版本)
  • 创建新项目文件 - &gt;新 - &gt;项目,在可扩展性组下选择:带代码修复的分析器(Nuget + VSIX)您必须以.NET Framework 4.6.2为目标来创建此项目。 enter image description here

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

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

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

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

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

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

答案 4 :(得分:1)

我为此找到了一个很棒的 Visual Studio 扩展/NuGet 包。让您不必像其他答案建议的那样自行创建。

https://github.com/ykoksen/unused-task-warning

安装 Visual Studio 扩展后,我转到分析/运行代码分析/解决方案,它发现了我遇到此问题的几个地方。

答案 5 :(得分:0)

您可以在VS Project属性中添加一个特定警告,引发编译错误,如here所述

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

这是VS2017的截图: VS2017 configuration to throw compiler errors for warning CS4014