Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to Microsoft.VisualStudio.Threading #28781

Merged
merged 20 commits into from
Aug 13, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cb37547
Enable vs-threading analyzers version 15.8.132
sharwell Jul 22, 2018
cf25962
Enable VSTHRD100 (Avoid async void methods) and fix violations
sharwell Jul 23, 2018
fbbf10d
Enable VSTHRD101 (Avoid unsupported async delegates) and fix violations
sharwell Jul 23, 2018
28103a3
Update to Microsoft.VisualStudio.Threading 15.5.32 for WithPriority(D…
sharwell Jul 23, 2018
1ab9e23
Merge remote-tracking branch 'dotnet/master' into vs-threading-analyzers
sharwell Jul 24, 2018
dc48c9b
Define IThreadingContext and provide it to ForegroundThreadAffinitize…
sharwell Jul 23, 2018
95032b5
Apply MefConstruction.ImportingConstructorMessage to constructors
sharwell Jul 23, 2018
059cd2f
Make LinkedFileUtilities a proper MEF component
sharwell Jul 24, 2018
ac2cfcb
Implement ThreadingContext and remove use of placeholder ThreadingCon…
sharwell Jul 24, 2018
2c85db1
Use JoinableTaskFactory (via IThreadingContext) for switching to the …
sharwell Jul 24, 2018
23c08dd
Use IThreadingContext.HasMainThread where appropriate
sharwell Jul 25, 2018
660762a
Merge remote-tracking branch 'dotnet/master' into vs-threading-analyzers
sharwell Aug 2, 2018
4aeda54
Call asynchronous methods from within asynchronous methods
sharwell Aug 2, 2018
bda85ce
Use WaitAndGetResult_CanCallOnBackground when the caller can be on a …
sharwell Aug 2, 2018
bec6816
Merge remote-tracking branch 'dotnet/master' into vs-threading-analyzers
sharwell Aug 3, 2018
cccd0d0
Use WaitAndGetResult_CanCallOnBackground when the caller can be on a …
sharwell Aug 4, 2018
4088da9
Completed migration of ForegroundThreadAffinitizedObject to JoinableT…
sharwell Jul 25, 2018
89a9992
Enable VSTHRD001 (Avoid legacy thread switching APIs) and address rem…
sharwell Jul 28, 2018
bb01d0e
Updates based on code review feedback
sharwell Aug 8, 2018
dc15c56
Merge remote-tracking branch 'dotnet/master' into vs-threading-analyzers
sharwell Aug 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions build/Rulesets/Roslyn_BuildRules.ruleset
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,12 @@ collection.
<Rule Id="VSSDK001" Action="Warning" /> <!-- Derive from AsyncPackage -->
<Rule Id="VSSDK003" Action="Warning" /> <!-- Support async tool windows -->
</Rules>
<Rules AnalyzerId="Microsoft.VisualStudio.Threading.Analyzers" RuleNamespace="Microsoft.VisualStudio.Threading.Analyzers">
<Rule Id="VSTHRD001" Action="None" /> <!-- Avoid legacy thread switching APIs -->
<Rule Id="VSTHRD002" Action="None" /> <!-- Avoid problematic synchronous waits -->
<Rule Id="VSTHRD101" Action="None" /> <!-- Avoid unsupported async delegates -->
<Rule Id="VSTHRD103" Action="None" /> <!-- Call async methods when in an async method -->
<Rule Id="VSTHRD110" Action="None" /> <!-- Observe result of async calls -->
<Rule Id="VSTHRD200" Action="None" /> <!-- Use "Async" suffix for async methods -->
</Rules>
</RuleSet>
1 change: 1 addition & 0 deletions build/Targets/Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
<MicrosoftVisualStudioTextManagerInterop100Version>10.0.30319</MicrosoftVisualStudioTextManagerInterop100Version>
<MicrosoftVisualStudioTextManagerInterop120Version>12.0.30110</MicrosoftVisualStudioTextManagerInterop120Version>
<MicrosoftVisualStudioTextManagerInterop121DesignTimeVersion>12.1.30328</MicrosoftVisualStudioTextManagerInterop121DesignTimeVersion>
<MicrosoftVisualStudioThreadingAnalyzersVersion>15.8.132</MicrosoftVisualStudioThreadingAnalyzersVersion>
<MicrosoftVisualStudioThreadingVersion>15.3.23</MicrosoftVisualStudioThreadingVersion>
<MicrosoftVisualStudioUtilitiesVersion>15.0.26730-alpha</MicrosoftVisualStudioUtilitiesVersion>
<MicrosoftVisualStudioValidationVersion>15.3.23</MicrosoftVisualStudioValidationVersion>
Expand Down
3 changes: 3 additions & 0 deletions build/Targets/Settings.props
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@
Condition="'$(OS)' == 'Windows_NT'" />

<Import Project="$(RoslynDiagnosticsPropsFilePath)" Condition="'$(UseRoslynAnalyzers)' == 'true' AND Exists('$(RoslynDiagnosticsPropsFilePath)')" />
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="$(MicrosoftVisualStudioThreadingAnalyzersVersion)" />
</ItemGroup>

<ImportGroup Label="WindowsImports" Condition="'$(OS)' == 'Windows_NT'">
<Import
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public Searcher(
}
}

internal async void Search()
internal async Task SearchAsync()
Copy link
Member

Choose a reason for hiding this comment

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

out of curiosity... why? This does seem like an appropriate use of async-void. No callers use or should use the return value here. And thsi function takes care of actually ensuring that the async-mechanism it is bridging to is handled.

Copy link
Member Author

@sharwell sharwell Aug 8, 2018

Choose a reason for hiding this comment

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

➡️ Two reasons:

  1. Previous signature failed to communicate the asynchronous nature of the operation
  2. async void is forbidden as more problematic than the benefits it is capable of providing

Copy link
Member

Choose a reason for hiding this comment

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

Previous signature failed to communicate the asynchronous nature of the operation

That was intentional. The caller was not support to be aware it was async. That's because it passed in a callback object, and it was hte responsibility of the called method to invoke the callback to support the external async pattern.

async void is forbidden as more problematic than the benefits it is capable of providing

I strongly disagree with this. 'async void' perfectly explains what is going on here. Furthermore, it acts as one of the best ways to mark a method as needing special handling. It would be far too easy to accidentally refactor things here and lead to a situation where cancellation or other exceptions was not handled properly. 'async void' has always been a great way for us to mark the boundary between '.net async' and other parts of our system that don't support that.

Copy link
Member Author

@sharwell sharwell Aug 8, 2018

Choose a reason for hiding this comment

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

➡️ This is now covered under the VSTHRD002 umbrella. Once enabled, we will have much better analyzer enforcement for the correct implementation of fire-and-forget operations than we do today, and a key part of this enforcement effort is blocking the use of async void. The special case is not needed, and works against the goals of consistent, and in this case enforceable, practices.

I understand that 'async void' may play an important goal in other projects, but in Roslyn it was exceedingly rare and did not have any particular meaning that needed to be preserved (due to replacement by analyzers). The easiest way to achieve consistency and clarity for average developers was eliminating the few cases where it was used.

I do not plan to reconsider this change for this pull request, but if you believe that it should be changed back we can open a new discussion (in a separate issue) to define a new set of consistency practices along with analyzer-enforced correctness as a proposed alternative to the direction seen here.

{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private void StartSearch(INavigateToCallback callback, string searchValue, IImmu
kinds,
_cancellationTokenSource.Token);

searcher.Search();
_ = searcher.SearchAsync();
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't this have an effect of silencing all exceptions?

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ It's an intermediate step. VSTHRD110 deals with observing the result of asynchronous operations.

Copy link
Member

Choose a reason for hiding this comment

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

@sharwell intermediate step still needs fixing further?

Copy link
Member Author

Choose a reason for hiding this comment

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

@jasonmalinowski Covered under the VSTHRD110 follow-up issue.

}

private static INavigateToSearchService_RemoveInterfaceAboveAndRenameThisAfterInternalsVisibleToUsersUpdate TryGetNavigateToSearchService(Project project)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.FindUsages;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
Expand Down Expand Up @@ -90,7 +91,7 @@ private bool TryExecuteCommand(int caretPosition, Document document, CommandExec
// a presenter that can accept streamed results.
if (streamingService != null && streamingPresenter != null)
{
StreamingFindReferences(document, caretPosition, streamingService, streamingPresenter);
_ = StreamingFindReferencesAsync(document, caretPosition, streamingService, streamingPresenter);
Copy link
Member

Choose a reason for hiding this comment

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

Why are we dropping a Task here?

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ This is part of the VSTHRD110 follow-up item mentioned in the original description. I also filed microsoft/vs-threading#345 to ensure we don't accidentally fail to address this.

return true;
}

Expand All @@ -109,14 +110,14 @@ private IStreamingFindUsagesPresenter GetStreamingPresenter()
}
}

private async void StreamingFindReferences(
private async Task StreamingFindReferencesAsync(
Copy link
Member

Choose a reason for hiding this comment

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

same comment as above. it seems better here to be async-void, to indicate that this is fire-and-forget. Namely, it helps ensure that this code knows that i needs to handle cancellation/exceptions. With Task-Returning, someone could easily remove the try/catch thinking it isn't necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ Fire-and-forget falls under the umbrella of the VSTHRD110 follow-up item, for which we will soon have analyzer enforcement. The complexity drawbacks of correct use of async void outweigh the potential benefits.

Copy link
Member

Choose a reason for hiding this comment

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

The complexity drawbacks of correct use of async void outweigh the potential benefits.

Sorry:

  1. By what basis are you making that claim? It seems to be an assertion without any argument even trying to back it up.
  2. It's unclear to me what complexity drawback you're even referring to.

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ Moved this discussion to #28781 (comment)

Document document, int caretPosition,
IFindUsagesService findUsagesService,
IStreamingFindUsagesPresenter presenter)
{
try
{
using (var token = _asyncListener.BeginAsyncOperation(nameof(StreamingFindReferences)))
using (var token = _asyncListener.BeginAsyncOperation(nameof(StreamingFindReferencesAsync)))
{
// Let the presented know we're starting a search. It will give us back
// the context object that the FAR service will push results into.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,11 @@ public void EndTracking()
}

private void DocumentOpened(object sender, DocumentEventArgs e)
=> DocumentOpenedAsync(e.Document);
{
_ = DocumentOpenedAsync(e.Document);
}

private async void DocumentOpenedAsync(Document document)
private async Task DocumentOpenedAsync(Document document)
Copy link
Member

Choose a reason for hiding this comment

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

same comment as above. being Task returning feels highly misleading and error prone. We've had people do this and not then handle cancellation/exceptions properly. 'async void' is a nice clear marker of "this is fire and forget, just async, no one will await, so you have to handle these cases yourself".

Copy link
Member Author

@sharwell sharwell Aug 8, 2018

Choose a reason for hiding this comment

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

➡️ Covered by fire-and-forget umbrella.

{
try
{
Expand Down Expand Up @@ -232,12 +234,12 @@ private void TrackActiveSpansNoLock(
SetTrackingSpansNoLock(document.Id, null);

// fire and forget:
RefreshTrackingSpansAsync(document, snapshot);
_ = RefreshTrackingSpansAsync(document, snapshot);
}
}
}

private async void RefreshTrackingSpansAsync(Document document, ITextSnapshot snapshot)
private async Task RefreshTrackingSpansAsync(Document document, ITextSnapshot snapshot)
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@ private async Task AnalyzeAsync()
}
}

public async void Unregister(Workspace workspace, bool blockingShutdown = false)
public void Unregister(Workspace workspace, bool blockingShutdown = false)
{
_ = UnregisterAsync(workspace, blockingShutdown);
}

private async Task UnregisterAsync(Workspace workspace, bool blockingShutdown)
{
Copy link
Member

Choose a reason for hiding this comment

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

based on previous comments, this actually feels like a bug. This code seems to be missing required try/catches, and so could tear things down if something goes wrong.

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ Covered under the VSTHRD110 (fire and forget) umbrella

Contract.ThrowIfFalse(workspace == _workspace);
_source.Cancel();
Copy link
Member

Choose a reason for hiding this comment

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

what happens if awaitin _analyzeTask below throws?

Copy link
Member Author

Choose a reason for hiding this comment

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

➡️ Prior to this pull request, the application would crash. With this pull request, the exception will be ignored. With the changes for VSTHRD110, I am guessing we will opt for the approach of NFW.

Expand Down
6 changes: 3 additions & 3 deletions src/EditorFeatures/Core/Shared/Utilities/ResettableDelay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ public ResettableDelay(int delayInMilliseconds, TaskScheduler foregroundTaskSche

if (foregroundTaskScheduler != null)
{
Task.Factory.SafeStartNew(() => StartTimer(continueOnCapturedContext: true), CancellationToken.None, foregroundTaskScheduler);
Task.Factory.SafeStartNew(() => StartTimerAsync(continueOnCapturedContext: true), CancellationToken.None, foregroundTaskScheduler);
}
else
{
StartTimer(continueOnCapturedContext: false);
_ = StartTimerAsync(continueOnCapturedContext: false);
}
}

Expand All @@ -48,7 +48,7 @@ public void Reset()
_lastSetTime = Environment.TickCount;
}

private async void StartTimer(bool continueOnCapturedContext)
private async Task StartTimerAsync(bool continueOnCapturedContext)
{
do
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ internal void HookAutoRestartEvent()
}
}

private async void ProcessExitedHandler(object _, EventArgs __)
private void ProcessExitedHandler(object sender, EventArgs e)
{
_ = ProcessExitedHandlerAsync();
}

private async Task ProcessExitedHandlerAsync()
{
try
{
Expand Down
2 changes: 1 addition & 1 deletion src/Scripting/CSharpTest/ScriptTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public void TestRunVoidScript()

[WorkItem(5279, "https://github.com/dotnet/roslyn/issues/5279")]
[Fact]
public async void TestRunExpressionStatement()
public async Task TestRunExpressionStatement()
{
var state = await CSharpScript.RunAsync(
@"int F() { return 1; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,9 @@ public override void Initialize(AnalysisContext context)
context.RegisterCompilationAction(AsyncAction);
}

#pragma warning disable VSTHRD100 // Avoid async void methods
private async void AsyncAction(CompilationAnalysisContext context)
#pragma warning restore VSTHRD100 // Avoid async void methods
{
context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.None));
await Task.FromResult(true);
Expand Down
15 changes: 0 additions & 15 deletions src/Workspaces/Core/Portable/Utilities/TaskExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,6 @@ namespace Roslyn.Utilities
[SuppressMessage("ApiDesign", "CA1068", Justification = "Matching TPL Signatures")]
internal static partial class TaskExtensions
{
/// <summary>
/// Use to explicitly indicate that you are not waiting for a task to complete
/// Observes the exceptions from it.
/// </summary>
public static async void FireAndForget(this Task task)
{
try
{
await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
}

public static T WaitAndGetResult<T>(this Task<T> task, CancellationToken cancellationToken)
{
#if DEBUG
Expand Down
2 changes: 1 addition & 1 deletion src/Workspaces/MSBuildTest/MSBuildWorkspaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3251,7 +3251,7 @@ public async Task TestAddRemoveMetadataReference_GAC()

[ConditionalFact(typeof(VisualStudioMSBuildInstalled))]
[Trait(Traits.Feature, Traits.Features.MSBuildWorkspace)]
public async void TestAddRemoveMetadataReference_ReferenceAssembly()
public async Task TestAddRemoveMetadataReference_ReferenceAssembly()
Copy link
Member

Choose a reason for hiding this comment

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

good catch! I missed that one.

{
CreateFiles(GetMultiProjectSolutionFiles()
.WithFile(@"CSharpProject\CSharpProject.csproj", Resources.ProjectFiles.CSharp.WithSystemNumerics));
Expand Down