-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Make change signature more async and remove UI dialogs from the service #47768
Conversation
bf57c7b
to
bf5ad54
Compare
@@ -97,7 +97,7 @@ internal ChangeSignatureResult ChangeSignature(Document document, int position, | |||
switch (context) | |||
{ | |||
case ChangeSignatureAnalysisSucceededContext changeSignatureAnalyzedSucceedContext: | |||
return ChangeSignatureWithContext(changeSignatureAnalyzedSucceedContext, cancellationToken); | |||
return ChangeSignatureWithContextAsync(changeSignatureAnalyzedSucceedContext, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the next commit, I make this async as well and move the wait up to the command handler
@@ -387,7 +385,7 @@ internal ChangeSignatureResult ChangeSignatureWithContext(ChangeSignatureAnalysi | |||
var notificationService = context.Solution.Workspace.Services.GetRequiredService<INotificationService>(); | |||
if (!notificationService.ConfirmMessageBox(FeaturesResources.This_symbol_has_related_definitions_or_references_in_metadata_Changing_its_signature_may_result_in_build_errors_Do_you_want_to_continue, severity: NotificationSeverity.Warning)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is moved in the next commit to the proper locations
bf5ad54
to
5144713
Compare
src/EditorFeatures/Core/Implementation/ChangeSignature/AbstractChangeSignatureCommandHandler.cs
Outdated
Show resolved
Hide resolved
src/EditorFeatures/Core/Implementation/ChangeSignature/AbstractChangeSignatureCommandHandler.cs
Outdated
Show resolved
Hide resolved
src/EditorFeatures/Core/Implementation/ChangeSignature/AbstractChangeSignatureCommandHandler.cs
Outdated
Show resolved
Hide resolved
src/EditorFeatures/Core/Implementation/ChangeSignature/AbstractChangeSignatureCommandHandler.cs
Outdated
Show resolved
Hide resolved
src/EditorFeatures/Core/Implementation/ChangeSignature/AbstractChangeSignatureCommandHandler.cs
Show resolved
Hide resolved
src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs
Show resolved
Hide resolved
src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeActionOperation.cs
Show resolved
Hide resolved
src/Features/Core/Portable/ChangeSignature/ChangeSignatureResult.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM. But def a few things i'd like tweaked before i sign off. but this is sooo much better :)
src/Features/Core/Portable/ChangeSignature/CannotChangeSignatureReason.cs
Show resolved
Hide resolved
src/EditorFeatures/DiagnosticsTestUtilities/ChangeSignature/ChangeSignatureTestState.cs
Outdated
Show resolved
Hide resolved
src/EditorFeatures/Core/Implementation/ChangeSignature/AbstractChangeSignatureCommandHandler.cs
Outdated
Show resolved
Hide resolved
{ | ||
return true; | ||
} | ||
context.OperationContext.UserCancellationToken).WaitAndGetResult(context.OperationContext.UserCancellationToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So where in this process does the options dialog get displayed? Because now that this is async, how are we getting back to the UI thread to display the dialog? Does this need a JTF-friendly wait here as opposed to WaitAndGetResult?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jasonmalinowski see the HandleResult method that runs after this WaitAndGetResult
https://github.com/dotnet/roslyn/pull/47768/files#diff-becc7ba48505ea7db15e854f845d4702e0d44964ec9a05d20b65c26572049596R86
The service just returns the failure kind and doesn't do any UI thread operations, so WaitAndGetResult is fine. Then when it completes since we're already on the UI thread, HandleResult can just display the notification without any switching.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And to add to that - the preview window is also part of HandleResult. If the result has preview changes then it will use the preview service to display them from the UI thread.
src/Features/Core/Portable/ChangeSignature/ChangeSignatureCodeAction.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, or at least much better than what was there. I can't say I care for the pattern where the GetOptions() call runs even if the analysis fails and returns null, and then the actual refactoring operation runs even if options are null and then returns a different failure object. It seems like we're having to propogate failures when instead we could have stopped earlier and not had to have code dealing with failure modes. I'm happy for that to be addressed in a follow-up PR though (or not, if it makes a mess of something else) since this is a vast improvement for what's there.
@@ -54,7 +54,7 @@ class C | |||
Using testState = ChangeSignatureTestState.Create(workspace) | |||
Dim history = testState.Workspace.GetService(Of ITextUndoHistoryRegistry)().RegisterHistory(testState.Workspace.Documents.First().GetTextBuffer()) | |||
testState.TestChangeSignatureOptionsService.UpdatedSignature = permutation | |||
Dim result = testState.ChangeSignature() | |||
Dim result = Await testState.ChangeSignatureAsync().ConfigureAwait(False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ConfigureAwait() shouldn't be required in tests -- did the analyzer run here?
@@ -248,10 +248,10 @@ void M() | |||
|
|||
using var testState = ChangeSignatureTestState.Create(XElement.Parse(workspaceXml)); | |||
testState.TestChangeSignatureOptionsService.UpdatedSignature = updatedSignature; | |||
var result = testState.ChangeSignature(); | |||
var result = await testState.ChangeSignatureAsync().ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the ConfigureAwait(false)
in tests. If the analyzer was flagging this, that's a bug.
return true; | ||
} | ||
restrictToDeclarations: false, | ||
cancellationToken).WaitAndGetResult(context.OperationContext.UserCancellationToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cancellationToken).WaitAndGetResult(context.OperationContext.UserCancellationToken); | |
cancellationToken).WaitAndGetResult(cancellationToken); |
Just to be consistent with which cancellation token is in use. Not that there aren't ways to use different ones, but those are tricky.
|
||
var finalSolution = result.UpdatedSolution; | ||
// UI thread bound operation to show the change signature dialog. | ||
var changeSignatureOptions = AbstractChangeSignatureService.GetChangeSignatureOptions(changeSignatureContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be null checked if the cancel button is clicked?
if (context is not ChangeSignatureAnalysisSucceededContext succeededContext) | ||
{ | ||
return null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels a bit strange that the callers don't just check for the failure and immediately abort.
@@ -81,39 +79,14 @@ internal abstract class AbstractChangeSignatureService : ILanguageService | |||
|
|||
public async Task<ImmutableArray<ChangeSignatureCodeAction>> GetChangeSignatureCodeActionAsync(Document document, TextSpan span, CancellationToken cancellationToken) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I admit I'm surprised this function is here versus just being inlined into the actual code fix provider. Not urgent to fix though.
@@ -248,23 +230,21 @@ internal ChangeSignatureResult ChangeSignatureWithContext(ChangeSignatureAnalysi | |||
|
|||
#nullable enable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything left in this file that isn't #nullable enabled or isn't easily updated?
@@ -26,19 +29,19 @@ public ChangeSignatureCodeAction(AbstractChangeSignatureService changeSignatureS | |||
public override object? GetOptions(CancellationToken cancellationToken) | |||
=> AbstractChangeSignatureService.GetChangeSignatureOptions(_context); | |||
|
|||
protected override Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(object options, CancellationToken cancellationToken) | |||
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(object options, CancellationToken cancellationToken) | |||
{ | |||
if (options is ChangeSignatureOptionsResult changeSignatureOptions && changeSignatureOptions != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (options is ChangeSignatureOptionsResult changeSignatureOptions && changeSignatureOptions != null) | |
if (options is ChangeSignatureOptionsResult changeSignatureOptions) |
If the first test is true, it can't be null.
merging, will address small feedback in followup |
When debugging roslyn master, I noticed that change signature was throwing in TryCreateUpdatedSolution as it expected to be called on the UI thread. However, this code path is triggered by GetOperationsAsync which is explicitly not running on the UI thread. The AbstractChangeSignatureService currently uses the UI thread as it pops up error messages and dialog boxes in certain cases.
There are two parts to the change:
3c9b43c, but not quite all (I should have started with 2 first but oh well).
5144713
Note that most of the file changes are replacing WpfFact with Fact, the meat of the changes are in the following files
AbstractChangeSignatureService.cs
ChangeSignatureCodeAction.cs
ChangeSignatureCodeActionOperation.cs
AbstractChangeSignatureCommandHandler.cs
Resolves #44198
Resolves #41121
Resolves #41783