Utiliser AsyncPackage pour charger des VSPackages en arrière-plan

Le chargement et l’initialisation d’un package VS peuvent entraîner des E/S de disque. Si ces E/S se produisent sur le thread d’interface utilisateur, cela peut entraîner des problèmes de réactivité. Pour résoudre ce problème, Visual Studio 2015 a introduit la classe qui active le AsyncPackage chargement du package sur un thread d’arrière-plan.

Créer un AsyncPackage

Vous pouvez commencer par créer un projet VSIX (fichier>nouveau>>projet Visual C#>Extensibility>VSIX Project) et ajouter un VSPackage au projet (cliquez avec le bouton droit sur le projet et ajoutez>un nouvel élément>>C# extensibility>Visual Studio Package). Vous pouvez ensuite créer vos services et ajouter ces services à votre package.

  1. Dériver le package de AsyncPackage.

  2. Si vous fournissez des services dont l’interrogation peut entraîner le chargement de votre package :

    Pour indiquer à Visual Studio que votre package est sécurisé pour le chargement en arrière-plan et pour choisir ce comportement, vous PackageRegistrationAttribute devez définir la propriété AllowsBackgroundLoading sur true dans le constructeur d’attribut.

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

    Pour indiquer à Visual Studio qu’il est sûr d’instancier votre service sur un thread d’arrière-plan, vous devez définir la IsAsyncQueryable propriété sur true dans le ProvideServiceAttribute constructeur.

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. Si vous chargez via des contextes d’interface utilisateur, vous devez spécifier PackageAutoLoadFlags.BackgroundLoad pour l’OR ProvideAutoLoadAttribute la valeur (0x2) dans les indicateurs écrits comme valeur de l’entrée de chargement automatique de votre package.

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. Si vous avez un travail d’initialisation asynchrone à effectuer, vous devez remplacer InitializeAsync. Supprimez la Initialize() méthode fournie par le modèle VSIX. (La Initialize() méthode dans AsyncPackage est scellée). Vous pouvez utiliser l’une des AddService méthodes pour ajouter des services asynchrones à votre package.

    REMARQUE : Pour appeler base.InitializeAsync(), vous pouvez modifier votre code source en :

    await base.InitializeAsync(cancellationToken, progress);
    
  5. Vous devez veiller à ne pas créer de RPC (appel de procédure distante) à partir de votre code d’initialisation asynchrone (dans InitializeAsync). Celles-ci peuvent se produire lorsque vous appelez GetService directement ou indirectement. Lorsque les charges de synchronisation sont requises, le thread d’interface utilisateur bloque l’utilisation JoinableTaskFactory. Le modèle de blocage par défaut désactive les RPC. Cela signifie que si vous tentez d’utiliser un RPC à partir de vos tâches asynchrones, vous interblocez si le thread d’interface utilisateur attend que votre package se charge. L’alternative générale consiste à marshaler votre code sur le thread d’interface utilisateur si nécessaire à l’aide d’un élément tel que la fabrique de SwitchToMainThreadAsync tâches pouvant être joint ou un autre mécanisme qui n’utilise pas de RPC. N’utilisez PAS ThreadHelper.Generic.Invoke ou bloquez généralement le thread appelant en attente d’accès au thread d’interface utilisateur.

    REMARQUE : Vous devez éviter d’utiliser GetService ou QueryService dans votre InitializeAsync méthode. Si vous devez les utiliser, vous devez d’abord basculer vers le thread d’interface utilisateur. L’alternative consiste à utiliser GetServiceAsync à partir de votre AsyncPackage (en le castant sur IAsyncServiceProvider.)

    C# : Créer un 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);
    }
}

Convertir un VSPackage existant en AsyncPackage

La majorité du travail est identique à la création d’un AsyncPackage. Suivez les étapes 1 à 5 ci-dessus. Vous devez également prendre une attention supplémentaire avec les recommandations suivantes :

  1. N’oubliez pas de supprimer le Initialize remplacement que vous aviez dans votre package.

  2. Évitez les interblocages : il peut y avoir des RPC masqués dans votre code. qui se produit maintenant sur un thread d’arrière-plan. Assurez-vous que si vous effectuez un RPC (par exemple, GetService), vous devez basculer vers le thread principal ou (2) utiliser la version asynchrone de l’API s’il en existe un (par exemple, GetServiceAsync).

  3. Ne basculez pas entre les threads trop fréquemment. Essayez de localiser le travail qui peut se produire dans un thread d’arrière-plan pour réduire le temps de chargement.

Interrogation de services à partir d’AsyncPackage

Un AsyncPackage peut ou non se charger de façon asynchrone en fonction de l’appelant. Par exemple,

  • Si l’appelant a appelé GetService ou QueryService (api synchrones) ou

  • Si l’appelant a appelé IVsShell ::LoadPackage (ou IVsShell5 ::LoadPackageWithContext) ou

  • La charge est déclenchée par un contexte d’interface utilisateur, mais vous n’avez pas spécifié le mécanisme de contexte de l’interface utilisateur peut vous charger de manière asynchrone

    votre package se charge ensuite de façon synchrone.

    Votre package a toujours l’opportunité (dans sa phase d’initialisation asynchrone) d’effectuer un travail hors du thread d’interface utilisateur, bien que le thread d’interface utilisateur soit bloqué pour l’achèvement de ce travail. Si l’appelant utilise IAsyncServiceProvider pour interroger de façon asynchrone votre service, votre chargement et l’initialisation sont effectués de manière asynchrone en supposant qu’ils ne bloquent pas immédiatement l’objet de tâche résultant.

    C# : Comment interroger le service de façon asynchrone :

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;