How to repro
- Install the following repro
AsyncPackageRepro.zip
[Guid("aae76547-10c6-4a2d-b33a-76ded02ac07b")]
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideAutoLoad(Constants.vsContextNoSolution, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class MyAsyncPackage : AsyncPackage
{
protected override async Task InitializeAsync(
CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
Trace.WriteLine("Executed when VS opens without a solution");
await JoinableTaskFactory.SwitchToMainThreadAsync();
Trace.WriteLine("Executed when PR list on GitHub pane has finished loading");
}
}
- Open Visual Studio 2015 with the
GitHub pane visible and the PR list loading
- The
await JoinableTaskFactory.SwitchToMainThreadAsync() line only returns once the PR list has completely finished refreshing (which can take a long time)
What is impacted
- This is a problem when installing the PR status bar UI (which must be done on the Main thread). The current PR sometimes don't appear for a long time.
- This will also be a problem for packages that auto-load in order to handle command visibility (e.g.
GitHubPackage and InlineReviewsPackage).
- Any other extensions that use
SwitchToMainThreadAsync when they auto-load
Notes
This hasn't been a problem in the past because we haven't been using the AllowsBackgroundLoading = true and PackageAutoLoadFlags.BackgroundLoad options. Developers are now being strongly encouraged to enable these options. We will potentially be delaying when other extensions wake up.
This isn't necessarily a problem when a command is executed because in this case InitializeAsync starts off on the Main thread and SwitchToMainThreadAsync is a nop. If however auto-load was started before the command is executed, it will likely still get blocked.
How to fix
Update: This appears to be the best solution:
protected override async Task InitializeAsync()
{
// When SwitchToMainThreadAsync is called, use Normal priority (not Background)
await JoinableTaskFactory.RunAsync(VsTaskRunContext.UIThreadNormalPriority, InitializeMenus);
}
async Task InitializeMenus()
{
// This doesn't require the Main thread
var menuService = (IMenuCommandService)(await GetServiceAsync(typeof(IMenuCommandService)));
await JoinableTaskFactory.SwitchToMainThreadAsync();
// This does require the Main thread
menuService.AddCommands(...);
}
This can be resolved from inside InitializeAsync by using the following:
Unfortunately the following doesn't seem to work. SwitchToMainThreadAsync still doesn't return until after the GitHub pane has finished refreshing (this can take a while).
// `JoinableTaskFactory` is a property on `AsyncPackage`
await JoinableTaskFactory
.WithPriority(VsTaskRunContext.UIThreadNormalPriority)
.SwitchToMainThreadAsync();
I've tried the following as a workaround:
public static Task RunOnMainThreadNormalPriority(Action action)
{
var service = (IVsTaskSchedulerService2)VsTaskLibraryHelper.ServiceInstance;
var scheduler = service.GetTaskScheduler((uint)VsTaskRunContext.UIThreadNormalPriority);
return Task.Factory.StartNew(action, default(CancellationToken),
TaskCreationOptions.HideScheduler, scheduler);
}
This seems to work in simple cases, but if the executed code calls GetService it will deadlock (I think that's what's happening). We still need to find a solution.
We should also consider refreshing the PR list at a lower priority in order not to block other extensions.
Related
How to repro
AsyncPackageRepro.zip
GitHubpane visible and the PR list loadingawait JoinableTaskFactory.SwitchToMainThreadAsync()line only returns once the PR list has completely finished refreshing (which can take a long time)What is impacted
GitHubPackageandInlineReviewsPackage).SwitchToMainThreadAsyncwhen they auto-loadNotes
This hasn't been a problem in the past because we haven't been using the
AllowsBackgroundLoading = trueandPackageAutoLoadFlags.BackgroundLoadoptions. Developers are now being strongly encouraged to enable these options. We will potentially be delaying when other extensions wake up.This isn't necessarily a problem when a command is executed because in this case
InitializeAsyncstarts off on the Main thread andSwitchToMainThreadAsyncis a nop. If however auto-load was started before the command is executed, it will likely still get blocked.How to fix
Update: This appears to be the best solution:
This can be resolved from insideInitializeAsyncby using the following:Unfortunately the following doesn't seem to work.
SwitchToMainThreadAsyncstill doesn't return until after theGitHubpane has finished refreshing (this can take a while).I've tried the following as a workaround:
This seems to work in simple cases, but if the executed code calls
GetServiceit will deadlock (I think that's what's happening). We still need to find a solution.We should also consider refreshing the PR list at a lower priority in order not to block other extensions.
Related