Найти не ожидаемые вызовы метода async
Я просто наткнулся на довольно опасный сценарий при переносе приложения 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<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.
}
}
}
Ответы
Ответ 1
В конце мы использовали 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);
}
Ответ 2
После некоторых трудностей с этой проблемой я решил создать Анализатор с исправлением кода для ее решения.
Код доступен здесь:
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 does not 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));
}
Ответ 3
Компилятор выдаст предупреждение CS4014, но он отправляется только в том случае, если вызывающим методом является async
.
Нет предупреждения:
Task CallingMethod() {
DoWhateverAsync();
// More code that eventually returns a task.
}
Предупреждение CS4014: поскольку этот вызов не ожидается, выполнение текущего метода продолжается до завершения вызова. Попробуйте применить оператор 'await' к результату вызова.
async Task CallingMethod() {
DoWhateverAsync();
}
Это не очень полезно в вашем конкретном случае, потому что вы должны найти все места, где вызывается DoWhateverAsync
, изменить их, чтобы получить предупреждение, а затем исправить код. Но вы хотели использовать предупреждение компилятора, чтобы найти эти вызовы в первую очередь.
Я предлагаю вам использовать Visual Studio, чтобы найти все случаи использования DoWhateverAsync
. Вам все равно придется изменить окружающий код, пройдя через предупреждения компилятора или обработав список использований.
Ответ 4
У вас есть несколько вариантов:
- Это простейшее решение "Caveman", использующее встроенную функцию поиска VS (CTRL + SHIFT + F), поиск в полном решении, также в разделе Параметры поиска установите флажок
Используйте регулярное выражение и используйте это регулярное выражение:
(?<!await|task(.*))\s([_a-zA-Z0-9\.])*Async\(
Предполагается, что вы опубликуете все свои асинхронные методы с ключевым словом Async, а вызов метода находится в одной строке, Если это не так, не используйте его (или не добавляйте недостающие проверки в выражение).
- Используйте сторонний инструмент для анализа кода, пакет Nuget. ReSharper очень популярен, и я считаю, что он способен обнаружить эту проблему или вы можете создать свои собственные правила.
- Мой выбор - использовать Roslyn (@Volker предоставил одно решение). Вы можете создать свой собственный набор правил с помощью решений по исправлению кода (значок лампочки покажет исправление кода), так что это лучшее.
- ОБНОВЛЕНИЕ: VS 2019 проверяет эту проблему по умолчанию и выдает предупреждения.
![enter image description here]()
Как использовать Roslyn:
- Вы должны установить .NET Compiler Platform SDK: здесь
- Использовать VS 2017 версии 15.2 (или выше)
- Создайте новый проект Файл → Новый → Проект, в группе Расширяемость выберите: Анализатор с исправлением кода (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 должен начать обнаруживать проблемы.
Ответ 5
Вы можете добавить конкретное предупреждение в свойствах проекта VS, как то, которое выдает ошибку компиляции, как описано здесь
Вы можете добавить список предупреждающих кодов, разделенных запятой, например. CS4014
, и компилятор не сможет скомпилироваться, если вы не ожидаете метода async
.
Вот скриншот с VS2017:
конфигурация VS2017 для выброса ошибок компилятора для предупреждения CS4014