Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Loading PR list blocks await JoinableTaskFactory.SwitchToMainThreadAsync #1537

@jcansdale

Description

@jcansdale

How to repro

  1. 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");
        }
    }
  1. Open Visual Studio 2015 with the GitHub pane visible and the PR list loading
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions