Skip to content
This repository has been archived by the owner on Nov 8, 2018. It is now read-only.

Don't use Thread.Sleep() analyzers - initial implementation #50

Open
wants to merge 22 commits into
base: master
Choose a base branch
from

Conversation

tmaczynski
Copy link
Contributor

@tmaczynski tmaczynski commented Sep 4, 2016

I've created two analyzers:

  • first which reports usage of Thread.Sleep() inside of asynchronous code (i.e. asynchronous methods, asynchronous anonymous functions or asynchronous anonymous methods).
  • second which reports usage of Thread.Sleep() anywhere
    Analyzers have a common code fix which fixes occurrences of Thread.Sleep() in async code. If Thread.Sleep is used in non async code, there's no obvious automatic fix for the code, so no codefix is proposed.

I've made a decision to treat "async void" code as asynchronous.

I think that the first analysis should be active by default.

I've updated version of Microsoft.CodeAnalysis in a separate commit - if I recall correctly, I needed some functionality from the newer version. I did not observe any regression.

I've also updated a version of Visual Studio in sln file in a separate commit. I guess that majority of people use the most recent version of VS2015 so I think that it's convenient to include that change in repo.

I don’t know whether contributors are listed somewhere. I added comments listing me as a contributor in my pull request, but feel free to remove them if you want.

@codecov-io
Copy link

codecov-io commented Sep 4, 2016

Codecov Report

Merging #50 into master will increase coverage by 0.47%.
The diff coverage is 93.45%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #50      +/-   ##
==========================================
+ Coverage   81.75%   82.22%   +0.47%     
==========================================
  Files          36       49      +13     
  Lines        2970     3741     +771     
  Branches      226      252      +26     
==========================================
+ Hits         2428     3076     +648     
- Misses        434      553     +119     
- Partials      108      112       +4
Impacted Files Coverage Δ
...ers/Usage/DontUseThreadSleepInAsyncCodeAnalyzer.cs 100% <100%> (ø)
...ageAnalyzers.Test/Usage/DontUseThreadSleepTests.cs 100% <100%> (ø)
...UsageAnalyzers/Usage/DontUseThreadSleepAnalyzer.cs 100% <100%> (ø)
...nalyzers.Test/Usage/DontUseThreadSleepTestsBase.cs 100% <100%> (ø)
...s.Test/Usage/DontUseThreadSleepInAsyncCodeTests.cs 100% <100%> (ø)
...yncUsageAnalyzers/Usage/UsageResources.Designer.cs 21.66% <22.22%> (ø)
...ageAnalyzers/Helpers/ExpressionSyntaxExtensions.cs 77.08% <77.08%> (ø)
...eAnalyzers/Usage/DontUseThreadSleepAnalyzerBase.cs 84.21% <84.21%> (ø)
.../DontUseThreadSleepCodeUniversalCodeFixProvider.cs 93.2% <93.2%> (ø)
...nalyzers/AsyncUsageAnalyzers/AnalyzerExtensions.cs 66.19% <0%> (-4.23%) ⬇️
... and 16 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1e9fb71...a49bd3a. Read the comment docs.

…Yield()`. Don't use 0 as an Thread.Sleep's argument in other tests
update DontUseThreadSleepCodeUniversalCodeFixProvider CodeAction's title
so that it mentions Task.Yield() as a possible fix
… in Thread.Sleep() method call

this includes code changes, new extension method and changes in
CodeFixProvider
return typeMetadata.Equals(propertySymbol.ContainingType) && (propertySymbol.Name == propertyName);
}

public static bool IsInsideAsyncCode(this ExpressionSyntax invocationExpression, ref SyntaxNode enclosingMethodOrFunctionDeclaration)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could probably use IMethodSymbol.IsAsync in this method.
Current approach is purely syntactic, so it should be faster (I don't know how much it matters).

{
context.RegisterCodeFix(
CodeAction.Create(
"Use `await Task.Delay(...)` or `await Task.Yield()`",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for having the same message for both here? How does VS render the backticks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there were two separate messages for code fixes, semantic model would had to be awaited when the code fix is registered which would degrade performance. Currently, semantic model is awaited only if a user applies code fix. I don't think that user experience will be much better if there are two separate message.

I can change the code to include two separate code fix messages if you do think that it's worth it.

Backticks are displayed literally in VS2015 Update 2.

I believe that either quotes or backticks should be used to quote code - IMHO it improves readability. I used backticks because I wanted to be consistent with the AvoidAsyncSuffixCodeFixProvider. On the other hand no quotation characters are used in UseConfigureAwaitCodeFixProvider.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea if it is worth it, just looked a bit strange with a roulette type code fix :)

var firstNodeWithCorrectSpan = root
.FindNode(diagnostic.Location.SourceSpan);
InvocationExpressionSyntax expression = firstNodeWithCorrectSpan
.DescendantNodesAndSelf()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know Roslyn well enough to know if firstNodeWithCorrectSpan is always not null here.

Copy link
Contributor Author

@tmaczynski tmaczynski Nov 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know Roslyn very well either, but I think that I can justify that expression and firstNodeWithCorrectSpan are not null.

  1. In DontUseThreadSleepAnalyzerBase, the SyntaxNodeAction is registered on SyntaxKind.InvocationExpression and reports analysis on InvocationExpression.Syntax's location
  2. I use diangnostic's location to assign value tofirstNodeWithCorrectSpan. firstNodeWithCorrectSpan is assigned using SyntaxNode.FindNode method which (by default) returns outermost node encompassing the given span (source: http://www.coderesx.com/roslyn/html/6497A5DA.htm).
  3. There must be a node of type InvocationExpressionSyntax among firstNodeWithCorrectSpan's descendant nodes because diagnostic was reported on using InvocationExpressionSyntax's location, so expression is not null.

…eturn true if and only if non-null value is assigned to out parameter
@tmaczynski
Copy link
Contributor Author

It's been a while since I've created this PR. Do you have more feedback? I would like to make further contributions to this project, but I don't know if it's still maintained.

@tmaczynski
Copy link
Contributor Author

I've moved DontUseThreadSleep.md and DontUseThreadSleepInAsyncCode.md to a documentation folder and updated them slightly.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants