Use AsyncPackage para carregar VSPackages em segundo plano

Carregar e inicializar um pacote VS pode resultar em E/S de disco. Se essa E/S acontecer no thread da interface do usuário, isso pode levar a problemas de capacidade de resposta. Para resolver isso, o Visual Studio 2015 introduziu a classe que permite o carregamento de AsyncPackage pacotes em um thread em segundo plano.

Criar um AsyncPackage

Você pode começar criando um projeto VSIX (File>New Project Visual C#Extensibility VSIX Project>) e adicionando um VSPackage ao projeto (clique com o botão direito do mouse no projeto e Add>New>Item>C#> item>Extensibility>>Visual Studio Package). Em seguida, você pode criar seus serviços e adicioná-los ao seu pacote.

  1. Derive o pacote de AsyncPackage.

  2. Se você estiver fornecendo serviços cuja consulta pode fazer com que seu pacote seja carregado:

    Para indicar ao Visual Studio que seu pacote é seguro para carregamento em segundo plano e para aceitar esse comportamento, você PackageRegistrationAttribute deve definir a propriedade AllowsBackgroundLoading como true no construtor de atributo.

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

    Para indicar ao Visual Studio que é seguro instanciar seu serviço em um thread em segundo plano, você deve definir a IsAsyncQueryable propriedade como true no ProvideServiceAttribute construtor.

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. Se você estiver carregando por meio de contextos de interface do usuário, especifique PackageAutoLoadFlags.BackgroundLoad para o OU o valor (0x2) nos sinalizadores gravados como o ProvideAutoLoadAttribute valor da entrada de carregamento automático do pacote.

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. Se você tiver trabalho de inicialização assíncrona a fazer, substitua InitializeAsynco . Remova o Initialize() método fornecido pelo modelo VSIX. (O Initialize() método em AsyncPackage é lacrado). Você pode usar qualquer um dos métodos para adicionar serviços assíncronos AddService ao seu pacote.

    Observação : para chamar base.InitializeAsync(), você pode alterar seu código-fonte para:

    await base.InitializeAsync(cancellationToken, progress);
    
  5. Você deve tomar cuidado para NÃO fazer RPCs (Chamada de Procedimento Remoto) a partir do código de inicialização assíncrona (em InitializeAsync). Isso pode ocorrer quando você liga GetService direta ou indiretamente. Quando as cargas de sincronização são necessárias, o thread da interface do usuário será bloqueado usando JoinableTaskFactoryo . O modelo de bloqueio padrão desabilita RPCs. Isso significa que, se você tentar usar um RPC de suas tarefas assíncronas, você entrará em bloqueio se o thread da interface do usuário estiver aguardando o carregamento do pacote. A alternativa geral é empacotar seu código para o thread da interface do usuário, se necessário, SwitchToMainThreadAsync usando algo como o Joinable Task Factory ou algum outro mecanismo que não use um RPC. NÃO use ThreadHelper.Generic.Invoke ou geralmente bloqueie o thread de chamada aguardando para chegar ao thread da interface do usuário.

    Observação : você deve evitar usar GetService ou QueryService em seu InitializeAsync método. Se você precisar usá-los, precisará alternar para o thread da interface do usuário primeiro. A alternativa é usar GetServiceAsync a partir do seu AsyncPackage (convertendo-o em IAsyncServiceProvider.)

    C#: Criar um 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);
    }
}

Converter um VSPackage existente em AsyncPackage

A maior parte do trabalho é o mesmo que criar um novo AsyncPackage. Siga as etapas 1 a 5 acima. Você também precisa tomar cuidado extra com as seguintes recomendações:

  1. Lembre-se de remover a substituição que Initialize você tinha em seu pacote.

  2. Evite deadlocks: pode haver RPCs ocultos em seu código. que agora acontecem em um thread de fundo. Certifique-se de que, se você estiver criando um RPC (por exemplo, GetService), precisará (1) alternar para o thread principal ou (2) usar a versão assíncrona da API, se existir uma (por exemplo, GetServiceAsync).

  3. Não alterne entre threads com muita frequência. Tente localizar o trabalho que pode acontecer em um thread em segundo plano para reduzir o tempo de carregamento.

Consultando serviços de AsyncPackage

Um AsyncPackage pode ou não ser carregado de forma assíncrona, dependendo do chamador. Por exemplo,

  • Se o chamador chamou GetService ou QueryService (ambas APIs síncronas) ou

  • Se o chamador chamou IVsShell::LoadPackage (ou IVsShell5::LoadPackageWithContext) ou

  • A carga é acionada por um contexto de interface do usuário, mas você não especificou que o mecanismo de contexto de interface do usuário pode carregá-lo de forma assíncrona

    em seguida, seu pacote será carregado de forma síncrona.

    Seu pacote ainda tem uma oportunidade (em sua fase de inicialização assíncrona) de fazer o trabalho fora do thread da interface do usuário, embora o thread da interface do usuário seja bloqueado para a conclusão desse trabalho. Se o chamador usar IAsyncServiceProvider para consultar seu serviço de forma assíncrona, sua carga e inicialização serão feitas de forma assíncrona, supondo que elas não bloqueiem imediatamente o objeto de tarefa resultante.

    C#: Como consultar o serviço de forma assíncrona:

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;