Skip to content

Switch to installing/uninstalling nuget packages in the BG instead of the UI thread#55671

Merged
CyrusNajmabadi merged 9 commits intodotnet:mainfrom
CyrusNajmabadi:bgNuget
Aug 23, 2021
Merged

Switch to installing/uninstalling nuget packages in the BG instead of the UI thread#55671
CyrusNajmabadi merged 9 commits intodotnet:mainfrom
CyrusNajmabadi:bgNuget

Conversation

@CyrusNajmabadi
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi commented Aug 17, 2021

Fixes AB#959448
Blocked on NuGet/Home#11021 where Nuget documents that what we are doing is explicitly ok.

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner August 17, 2021 15:46
@ghost ghost added the Area-IDE label Aug 17, 2021
private async Task<T?> PerformNuGetProjectServiceWorkAsync<T>(
Func<INuGetProjectService, CancellationToken, ValueTask<T?>> doWorkAsync, CancellationToken cancellationToken)
{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
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 don't know if this is necessary. but i was trying to preserve prior semantics. @AArnott do you need to be on the UI thread to call .GetServiceAsync to get the IBrokeredServiceContainer?

Copy link
Contributor

Choose a reason for hiding this comment

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

No. GetServiceAsync can be called from a background thread. But it's generally not safe to cast the result as you're doing unless you are on the main thread or know that the service object is an MTA object. In this case, it is an MTA so you're fine.

private async Task<T?> PerformNuGetProjectServiceWorkAsync<T>(
Func<INuGetProjectService, CancellationToken, ValueTask<T?>> doWorkAsync, CancellationToken cancellationToken)
{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Copy link
Contributor

Choose a reason for hiding this comment

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

No. GetServiceAsync can be called from a background thread. But it's generally not safe to cast the result as you're doing unless you are on the main thread or know that the service object is an MTA object. In this case, it is an MTA so you're fine.

{
await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

var serviceContainer = (IBrokeredServiceContainer?)await _asyncServiceProvider.GetServiceAsync(typeof(SVsBrokeredServiceContainer)).ConfigureAwait(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

But because of that caveat, we recommend folks switch to using the extension method:

Suggested change
var serviceContainer = (IBrokeredServiceContainer?)await _asyncServiceProvider.GetServiceAsync(typeof(SVsBrokeredServiceContainer)).ConfigureAwait(false);
IBrokeredServiceContainer? serviceContainer = await _asyncServiceProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>(throwOnFailure: false).ConfigureAwait(false);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. GetServiceAsync can be called from a background thread. But it's generally not safe to cast the result as you're doing unless you are on the main thread or know that the service object is an MTA object. In this case, it is an MTA so you're fine.

Ah ok.

Copy link
Contributor

@sharwell sharwell Aug 19, 2021

Choose a reason for hiding this comment

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

In this case, it is an MTA so you're fine.

@AArnott I wasn't able to find the documentation for this. Can you link it?

But because of that caveat, we recommend folks switch to using the extension method

The last time we discussed this, the extension method is incompatible with unit testing because we cannot provide the correct JTF instance (ThreadHelper cannot be used in test suites that rely on state reset for test isolation). Has this been corrected?

Copy link
Contributor

Choose a reason for hiding this comment

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

Has this been corrected?

No, I don't think so. I use vssdktestfx which sets the statics so that it works, but I respect the goal of pure test isolation and we should offer an overload that I guess takes a JoinableTaskContext object.

workspace, _document.Id, _source, _packageName,
_versionOpt, _includePrerelease, cancellationToken);
_versionOpt, _includePrerelease,
progressTracker, cancellationToken);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

once #55682 goes in, this can be async end to end.

Copy link
Contributor

@sharwell sharwell left a comment

Choose a reason for hiding this comment

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

Had a question

Comment on lines +409 to +410
// explicitly switch to BG thread to do the installation as nuget installer APIs are free threaded.
await TaskScheduler.Default;
Copy link
Contributor

@sharwell sharwell Aug 19, 2021

Choose a reason for hiding this comment

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

💡 This is going to switch to the background thread and then immediately switch back to the main thread to update the status bar. This line should be moved down to right before the try call to UninstallPackage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

seems reasonable. but in that case, why do we need the switch at all? the UpdateStatusBarAsync call has CA(false) on it. So we'll be off hte UI thread then, right?

Copy link
Contributor

@sharwell sharwell left a comment

Choose a reason for hiding this comment

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

Overall the change is fine as a refactoring. Since we are blocked on NuGet/Home#11021 for the current form, I recommend the following:

  1. Add explicit calls to SwitchToMainThreadAsync before the NuGet operations which are not yet documented (there are three locations for this which are given as comments above)
  2. Retitle this pull request to indicate that it is a refactoring, since it will no longer be changing the threads used for installation/uninstallation of packages
  3. File a follow-up issue to change the SwitchToMainThreadAsync calls over to TaskScheduler.Default once the blocking issue is addressed

This approach will expedite this pull request by side-stepping the blocking issue.

@sharwell sharwell changed the base branch from main to main-vs-deps August 20, 2021 16:24
@sharwell sharwell requested a review from a team August 20, 2021 16:24
@sharwell sharwell changed the base branch from main-vs-deps to main August 20, 2021 16:24
@sharwell sharwell changed the base branch from main to main-vs-deps August 20, 2021 16:25
@sharwell sharwell changed the base branch from main-vs-deps to main August 20, 2021 16:25
@CyrusNajmabadi
Copy link
Contributor Author

image

:'(

Copy link
Contributor

@sharwell sharwell left a comment

Choose a reason for hiding this comment

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

Looks good now that NuGet/NuGet.Client#4209 is moving forward

@CyrusNajmabadi
Copy link
Contributor Author

@sharwell do you want me to wait on NuGet/NuGet.Client#4209 being merged? or are you ok knowing it's in progress?

@CyrusNajmabadi
Copy link
Contributor Author

NuGet/NuGet.Client#4209 merged in. Merging this in now.

@CyrusNajmabadi CyrusNajmabadi merged commit e079a36 into dotnet:main Aug 23, 2021
@ghost ghost added this to the Next milestone Aug 23, 2021
@CyrusNajmabadi CyrusNajmabadi deleted the bgNuget branch August 23, 2021 18:30
@dibarbet dibarbet modified the milestones: Next, 17.0.P4 Aug 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants