Использование AsyncPackage для загрузки VSPackage в фоновом режиме

Загрузка и инициализация пакета VS может привести к вводу-выводу диска. Если такие ввода-вывода происходят в потоке пользовательского интерфейса, это может привести к проблемам с реагированием. Чтобы устранить эту проблему, Visual Studio 2015 представила AsyncPackage класс, который позволяет загружать пакет в фоновом потоке.

Создание AsyncPackage

Для начала можно создать проект VSIX (Файл>нового>>проекта Visual C#>Extensibility>VSIX Project) и добавить VSPackage в проект (щелкните проект правой кнопкой мыши и добавьте>пакет> Visual Studio расширения>элемента>C#). Затем можно создать службы и добавить эти службы в пакет.

  1. Производный пакет от AsyncPackage.

  2. Если вы предоставляете службы, запросы которых могут привести к загрузке пакета:

    Чтобы указать Visual Studio, что пакет является безопасным для фоновой загрузки и для этого поведения, необходимо PackageRegistrationAttribute задать для свойства AllowsBackgroundLoading значение true в конструкторе атрибутов.

    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
    
    

    Чтобы указать Visual Studio, что оно безопасно для создания экземпляра службы в фоновом потоке, необходимо задать IsAsyncQueryable для свойства значение true в конструкторе ProvideServiceAttribute .

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. При загрузке с помощью контекстов пользовательского интерфейса необходимо указать PackageAutoLoadFlags.BackgroundLoad для ProvideAutoLoadAttribute параметра OR значение (0x2) в флаги, записанные в качестве значения записи автоматической загрузки пакета.

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. Если у вас есть асинхронная инициализация, необходимо переопределить InitializeAsync. Удалите метод, предоставленный Initialize() шаблоном VSIX. (Метод Initialize() в AsyncPackage запечатан). Вы можете использовать любой из AddService методов для добавления асинхронных служб в пакет.

    ПРИМЕЧАНИЕ. Чтобы вызвать base.InitializeAsync(), можно изменить исходный код следующим образом:

    await base.InitializeAsync(cancellationToken, progress);
    
  5. Необходимо не делать RPCs (удаленный вызов процедуры) из кода асинхронной инициализации (в InitializeAsync). Они могут возникать при вызове GetService напрямую или косвенно. При необходимости синхронизации поток пользовательского интерфейса блокирует использование JoinableTaskFactory. Модель блокировки по умолчанию отключает RPC. Это означает, что если вы пытаетесь использовать RPC из асинхронных задач, вы будете взаимоблокировки, если поток пользовательского интерфейса ожидает загрузки пакета. Общая альтернатива — маршалировать код в поток пользовательского интерфейса при необходимости с помощью такого механизма, как фабрика задачSwitchToMainThreadAsync joinable или другой механизм, который не использует RPC. Не используйте ThreadHelper.Generic.Invoke или обычно блокируйте вызывающий поток, ожидая получения потока пользовательского интерфейса.

    ПРИМЕЧАНИЕ. В методе не следует использовать GetService или QueryServiceInitializeAsync. Если их необходимо использовать, сначала необходимо переключиться на поток пользовательского интерфейса. Альтернативой является использование GetServiceAsync из AsyncPackage (путем приведения его к IAsyncServiceProvider.)

    C#: создайте AsyncPackage:

[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
public sealed class TestPackage : AsyncPackage
{
    protected override Task InitializeAsync(System.Threading.CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        this.AddService(typeof(SMyTestService), CreateService, true);
        return Task.FromResult<object>(null);
    }
}

Преобразование существующего VSPackage в AsyncPackage

Большая часть работы аналогична созданию нового AsyncPackage. Выполните шаги 1–5 выше. Кроме того, необходимо принять дополнительную осторожность со следующими рекомендациями:

  1. Не забудьте удалить Initialize переопределение, который у вас был в пакете.

  2. Избегайте взаимоблокировок: в коде могут быть скрытые RPCs. которые теперь происходят в фоновом потоке. Убедитесь, что если вы делаете RPC (например, GetService), необходимо переключиться на основной поток или (2) использовать асинхронную версию API, если она существует (например, GetServiceAsync).

  3. Не переключайтесь между потоками слишком часто. Попробуйте локализовать работу, которая может произойти в фоновом потоке, чтобы сократить время загрузки.

Запрос служб из AsyncPackage

AsyncPackage может асинхронно загружаться в зависимости от вызывающего объекта. Например,

  • Если вызывающий объект называется GetService или QueryService (оба синхронных API) или

  • Если вызывающий объект называется IVsShell::LoadPackage (или IVsShell5::LoadPackageWithContext) или

  • Загрузка активируется контекстом пользовательского интерфейса, но механизм контекста пользовательского интерфейса не указан для асинхронной загрузки.

    затем пакет будет синхронно загружаться.

    Пакет по-прежнему имеет возможность (на этапе асинхронной инициализации) работать с потоком пользовательского интерфейса, хотя поток пользовательского интерфейса будет заблокирован для завершения этой работы. Если вызывающий объект использует IAsyncServiceProvider для асинхронного запроса к службе, загрузка и инициализация будет выполнена асинхронно, если они не сразу блокируют результирующий объект задачи.

    C#: как асинхронно запрашивать службу:

using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

IAsyncServiceProvider asyncServiceProvider = Package.GetService(typeof(SAsyncServiceProvider)) as IAsyncServiceProvider;
IMyTestService testService = await asyncServiceProvider.GetServiceAsync(typeof(SMyTestService)) as IMyTestService;