web-dev-qa-db-fra.com

Rechercher des appels de méthode asynchrone non attendus

Je viens de tomber sur un scénario plutôt dangereux lors de la migration d'une application ASP.NET vers le modèle async/wait.

La situation est que j'ai créé une méthode asynchrone: async Task DoWhateverAsync(), modifié la déclaration dans l'interface en Task DoWhateverAsync() et espéré que le compilateur me dirait où le code est maintenant incorrect, via que Warning . Eh bien, pas de chance. Où que cet objet soit injecté via l'interface, l'avertissement ne se produit pas. :-(

C'est dangereux. Est-il possible de rechercher automatiquement des méthodes non attendues qui renvoient des tâches? Quelques avertissements ne me dérangent pas trop, mais je ne voudrais pas en manquer un.

Voici un exemple:

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

En fin de compte, nous avons utilisé roslyn pour rechercher toutes les occurrences dans lesquelles une valeur de retour de tâche ou de tâche <> a été ignorée:

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

Après de nombreuses difficultés avec ce problème, j'ai décidé de créer un analyseur avec correction de code pour le résoudre.

Le code est disponible ici: https://github.com/ykoksen/unused-task-warning

C'est aussi un paquet NuGet qui peut être utilisé comme analyseur pour un projet (quand il est construit): https://www.nuget.org/packages/Lindhart.Analyser.MissingAwaitWarning/

En outre, il est également disponible en tant qu'extension Visual Studio (pour 2017). Cependant, cela ne concerne que les fichiers actuellement ouverts, je vous recommande donc d'utiliser le paquet NuGet. L'extension est disponible ici (ou recherchez-la dans Visual Studio): https://marketplace.visualstudio.com/items?itemName=Lindhart.missingAwaitWarning#overview

Le code de l'analyseur:

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

Le compilateur émettra warning CS4014 mais il n’est émis que si la méthode d’appel est async.

Pas d'avertissement:

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

Avertissement CS4014: Cet appel n'étant pas attendu, l'exécution de la méthode en cours se poursuit avant la fin de l'appel. Pensez à appliquer l'opérateur 'wait' au résultat de l'appel.

async Task CallingMethod() {
    DoWhateverAsync();
}

Ce n’est pas très utile dans votre cas particulier, car vous devez rechercher tous les emplacements où DoWhateverAsync est appelé, les modifier pour obtenir l’avertissement, puis corriger le code. Mais vous vouliez utiliser l'avertissement du compilateur pour rechercher ces appels en premier lieu.

Je vous suggère d'utiliser Visual Studio pour rechercher toutes les utilisations de DoWhateverAsync. Quoi qu'il en soit, vous devrez modifier le code environnant en suivant les avertissements du compilateur ou en élaborant une liste d'utilisations.

6
Martin Liversage

Vous avez quelques options:

  • C’est la solution "Caveman" la plus simple qui utilise la fonctionnalité de recherche intégrée (CTRL + SHIFT + F) de la recherche dans la solution entière, également sous Options de recherche cliquez sur la case à cocher Utiliser Expression régulière et utiliser cette expression rationnelle: (?<!await|task(.*))\s([_a-zA-Z0-9\.])*Async\( Cela suppose que vous avez {votre publication a corrigé toutes vos méthodes asynchrones avec le mot-clé Async) et le l'appel de méthode est sur une seule ligne Si ce n'est pas vrai, ne l'utilisez pas (ou ajoutez les validations manquantes à l'expression).
  • Utilisez un outil d'analyse de code tiers, le package Nuget. ReSharper est très populaire et je crois qu’il est capable de détecter ces problèmes ou de créer vos propres règles.
  • Mon choix serait d’utiliser Roslyn (@Volker a fourni une solution). Vous pouvez créer votre propre ensemble de règles avec des solutions de correction de code (l'icône de l'ampoule indique votre correction de code), donc c'est la meilleure solution.

Comment utiliser Roslyn:

  • Vous devez installer le Kit de développement .NET Compiler Platform: à partir de ici
  • Utilisez VS 2017 version 15.2 (ou supérieure)
  • Créez un nouveau projet Fichier -> Nouveau -> Projet, dans le groupe Extensibility, sélectionnez: Analyseur avec correction de code (Nuget + VSIX)}. Vous devez cibler .NET Framework 4.6.2 pour créer ce projet. ____.]  enter image description here

Vous pouvez copier coller la solution précédente. Créer

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

classe avec logique, pour détecter le problème. Et créer 

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

classe pour fournir des suggestions de réparation (ajouter wait) au problème.

Après une construction réussie, vous obtiendrez votre propre paquet .wsix, vous pourrez l’installer sur votre instance VS. Après un redémarrage du VS, vous devriez commencer à résoudre les problèmes.

4
Major

Vous pouvez ajouter un avertissement spécifique dans les propriétés du projet VS comme générant une erreur de compilation, comme décrit ici

Vous pouvez ajouter une liste de codes d'avertissement, séparés par des points-virgules, par exemple. CS4014, et la compilation du compilateur échouera si vous n'attendez pas une méthode async.

Voici une capture d’écran avec VS2017: La configuration VS2017 génère des erreurs de compilation pour l’avertissement CS4014

0
theDurbari