Usare AsyncPackage per caricare pacchetti VSPackage in background

Il caricamento e l'inizializzazione di un pacchetto di Visual Studio possono comportare operazioni di I/O su disco. Se tale I/O si verifica nel thread dell'interfaccia utente, può causare problemi di velocità di risposta. Per risolvere questo problema, Visual Studio 2015 ha introdotto la classe che abilita il AsyncPackage caricamento dei pacchetti in un thread in background.

Creare un pacchetto asincrono

È possibile iniziare creando un progetto VSIX (File>Nuovo>>progetto Visual C#>Extensibility>VSIX Project) e aggiungendo un PACCHETTO VSPackage al progetto (fare clic con il pulsante destro del mouse sul progetto e aggiungi>nuovo>elemento>C# Extensibility>Visual Studio Package). È quindi possibile creare i servizi e aggiungere tali servizi al pacchetto.

  1. Derivare il pacchetto da AsyncPackage.

  2. Se si forniscono servizi la cui query può causare il caricamento del pacchetto:

    Per indicare a Visual Studio che il pacchetto è sicuro per il caricamento in background e per acconsentire esplicitamente a questo comportamento, impostare PackageRegistrationAttribute la proprietà AllowsBackgroundLoading su true nel costruttore dell'attributo.

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

    Per indicare a Visual Studio che è possibile creare un'istanza del servizio in un thread in background, è necessario impostare la IsAsyncQueryable proprietà su true nel ProvideServiceAttribute costruttore.

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. Se si caricano tramite contesti dell'interfaccia utente, è necessario specificare PackageAutoLoadFlags.BackgroundLoad per or il ProvideAutoLoadAttribute valore (0x2) nei flag scritti come valore della voce di caricamento automatico del pacchetto.

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. Se si dispone di operazioni di inizializzazione asincrone da eseguire, è necessario eseguire l'override di InitializeAsync. Rimuovere il Initialize() metodo fornito dal modello VSIX. (Il Initialize() metodo in AsyncPackage è sealed). È possibile usare uno dei AddService metodi per aggiungere servizi asincroni al pacchetto.

    NOTA: per chiamare base.InitializeAsync(), è possibile modificare il codice sorgente in:

    await base.InitializeAsync(cancellationToken, progress);
    
  5. È necessario prestare attenzione a NON effettuare RPC (Remote Procedure Call) dal codice di inizializzazione asincrona (in InitializeAsync). Questi possono verificarsi quando si chiama GetService direttamente o indirettamente. Quando sono necessari carichi di sincronizzazione, il thread dell'interfaccia utente bloccherà l'uso di JoinableTaskFactory. Il modello di blocco predefinito disabilita le rpc. Ciò significa che se si tenta di usare una RPC dalle attività asincrone, si eseguirà il deadlock se il thread dell'interfaccia utente è in attesa del caricamento del pacchetto. L'alternativa generale consiste nel effettuare il marshalling del codice al thread dell'interfaccia SwitchToMainThreadAsync utente, se necessario, usando un meccanismo simile a Joinable Task Factory o ad altri meccanismi che non usano rpc. Non usare ThreadHelper.Generic.Invoke o in genere bloccare il thread chiamante in attesa di accedere al thread dell'interfaccia utente.

    NOTA: è consigliabile evitare di usare GetService o QueryService nel InitializeAsync metodo. Se devi usarli, dovrai prima passare al thread dell'interfaccia utente. L'alternativa consiste nell'usare GetServiceAsync da AsyncPackage (eseguendo il cast a IAsyncServiceProvider.)

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

Convertire un VSPackage esistente in AsyncPackage

La maggior parte del lavoro equivale alla creazione di un nuovo AsyncPackage. Seguire i passaggi da 1 a 5 sopra. È anche necessario prestare particolare attenzione con le raccomandazioni seguenti:

  1. Ricordarsi di rimuovere l'override Initialize presente nel pacchetto.

  2. Evitare deadlock: potrebbero essere presenti controller di dominio nascosti nel codice. che ora avviene su un thread in background. Assicurarsi che se si sta creando una rpc (ad esempio, GetService), è necessario passare al thread principale o (2) usare la versione asincrona dell'API, ad esempio GetServiceAsync.

  3. Non passare da un thread all'altro troppo frequentemente. Provare a localizzare il lavoro che può verificarsi in un thread in background per ridurre il tempo di caricamento.

Esecuzione di query sui servizi da AsyncPackage

Un AsyncPackage può o meno caricare in modo asincrono a seconda del chiamante. Ad esempio:

  • Se il chiamante ha chiamato GetService o QueryService (API sincrone) o

  • Se il chiamante ha chiamato IVsShell::LoadPackage (o IVsShell5::LoadPackageWithContext) o

  • Il caricamento viene attivato da un contesto dell'interfaccia utente, ma non è stato specificato il meccanismo di contesto dell'interfaccia utente può essere caricato in modo asincrono

    il pacchetto caricherà in modo sincrono.

    Il pacchetto ha ancora la possibilità (nella fase di inizializzazione asincrona) di eseguire il lavoro fuori dal thread dell'interfaccia utente, anche se il thread dell'interfaccia utente verrà bloccato per il completamento del lavoro. Se il chiamante usa IAsyncServiceProvider per eseguire query asincrone per il servizio, il carico e l'inizializzazione verranno eseguiti in modo asincrono presupponendo che non blocchino immediatamente sull'oggetto attività risultante.

    C#: Come eseguire query sul servizio in modo asincrono:

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;