Condividi tramite


Fornire un servizio di Visual Studio asincrono

Se si vuole ottenere un servizio senza bloccare il thread dell'interfaccia utente, è necessario creare un servizio asincrono e caricare il pacchetto in un thread in background. A questo scopo, è possibile usare un oggetto AsyncPackage anziché un Packagee aggiungere il servizio con i metodi asincroni speciali del pacchetto asincrono.

Per informazioni sulla fornitura di servizi di Visual Studio sincroni, vedere Procedura: Fornire un servizio.

Implementare un servizio asincrono

  1. Creare un progetto VSIX (File>New>Project>Visual C#>Extensiblity>VSIX Project). Assegnare al progetto il nome TestAsync.

  2. Aggiungere un PACCHETTO VSPackage al progetto. Selezionare il nodo del progetto nel Esplora soluzioni e fare clic su Aggiungi>nuovo elemento Visual C# Items>Extensibility Visual Studio Package (Aggiungi nuovo elemento>Visual C# Extensibility>Visual Studio Package). Assegnare al file il nome TestAsyncPackage.cs.

  3. In TestAsyncPackage.cs modificare il pacchetto in modo che erediti da AsyncPackage anziché Packageda :

    public sealed class TestAsyncPackage : AsyncPackage
    
  4. Per implementare un servizio, è necessario creare tre tipi:

    • Interfaccia che identifica il servizio. Molte di queste interfacce sono vuote, ovvero non hanno metodi perché vengono usati solo per l'esecuzione di query sul servizio.

    • Interfaccia che descrive l'interfaccia del servizio. Questa interfaccia include i metodi da implementare.

    • Classe che implementa sia il servizio che l'interfaccia del servizio.

  5. L'esempio seguente illustra un'implementazione molto semplice dei tre tipi. Il costruttore della classe del servizio deve impostare il provider di servizi. In questo esempio si aggiungerà semplicemente il servizio al file di codice del pacchetto.

  6. Aggiungere le direttive using seguenti al file del pacchetto:

    using System.Threading;
    using System.Threading.Tasks;
    using System.Runtime.CompilerServices;
    using System.IO;
    using Microsoft.VisualStudio.Threading;
    using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider;
    using Task = System.Threading.Tasks.Task;
    
  7. Ecco l'implementazione asincrona del servizio. Si noti che è necessario impostare il provider di servizi asincrono anziché il provider di servizi sincrono nel costruttore:

    public class TextWriterService : STextWriterService, ITextWriterService
    {
        private IAsyncServiceProvider asyncServiceProvider;
    
        public TextWriterService(IAsyncServiceProvider provider)
        {
            // constructor should only be used for simple initialization
            // any usage of Visual Studio service, expensive background operations should happen in the
            // asynchronous InitializeAsync method for best performance
            asyncServiceProvider = provider;
        }
    
        public async Task InitializeAsync(CancellationToken cancellationToken)
        {
            await TaskScheduler.Default;
            // do background operations that involve IO or other async methods
    
            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            // query Visual Studio services on main thread unless they are documented as free threaded explicitly.
            // The reason for this is the final cast to service interface (such as IVsShell) may involve COM operations to add/release references.
    
            IVsShell vsShell = this.asyncServiceProvider.GetServiceAsync(typeof(SVsShell)) as IVsShell;
            // use Visual Studio services to continue initialization
        }
    
        public async Task WriteLineAsync(string path, string line)
        {
            StreamWriter writer = new StreamWriter(path);
            await writer.WriteLineAsync(line);
            writer.Close();
        }
    }
    
    public interface STextWriterService
    {
    }
    
    public interface ITextWriterService
    {
        System.Threading.Tasks.Task WriteLineAsync(string path, string line);
    }
    

Registrare un servizio

Per registrare un servizio, aggiungere l'oggetto ProvideServiceAttribute al pacchetto che fornisce il servizio. Diversamente dalla registrazione di un servizio sincrono, è necessario assicurarsi che sia il pacchetto che il servizio supportino il caricamento asincrono:

  • È necessario aggiungere il campo AllowsBackgroundLoading = true a PackageRegistrationAttribute per assicurarsi che il pacchetto possa essere inizializzato in modo asincrono. Per altre informazioni su PackageRegistrationAttribute, vedere Registrare e annullare la registrazione di PACCHETTI VSPackage.

  • È necessario aggiungere il campo IsAsyncQueryable = true a per assicurarsi che l'istanza ProvideServiceAttribute del servizio possa essere inizializzata in modo asincrono.

    Di seguito è riportato un esempio di con AsyncPackage una registrazione asincrona del servizio:

[ProvideService((typeof(STextWriterService)), IsAsyncQueryable = true)]
[ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid(TestAsyncPackage.PackageGuidString)]
public sealed class TestAsyncPackage : AsyncPackage
{. . . }

Aggiungere un servizio

  1. In TestAsyncPackage.cs rimuovere il metodo ed eseguire l'override Initialize() del InitializeAsync() metodo . Aggiungere il servizio e aggiungere un metodo di callback per creare i servizi. Di seguito è riportato un esempio dell'inizializzatore asincrono che aggiunge un servizio:

    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await base.InitializeAsync(cancellationToken, progress);
        this.AddService(typeof(STextWriterService), CreateTextWriterService);
    }
    
    

    Per rendere visibile questo servizio all'esterno di questo pacchetto, impostare il valore del flag promote su true come ultimo parametro: this.AddService(typeof(STextWriterService), CreateTextWriterService, true);

  2. Aggiungere un riferimento a Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll.

  3. Implementare il metodo di callback come metodo asincrono che crea e restituisce il servizio.

    public async Task<object> CreateTextWriterService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType)
    {
        TextWriterService service = new TextWriterService(this);
        await service.InitializeAsync(cancellationToken);
        return service;
    }
    
    

Usare un servizio

È ora possibile ottenere il servizio e usare i relativi metodi.

  1. Questo verrà visualizzato nell'inizializzatore, ma è possibile ottenere il servizio ovunque si voglia usare il servizio.

    protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await base.InitializeAsync(cancellationToken, progress);
        this.AddService(typeof(STextWriterService), CreateTextWriterService);
    
        ITextWriterService textService = await this.GetServiceAsync(typeof(STextWriterService)) as ITextWriterService;
        string userpath = @"C:\MyDir\MyFile.txt";
        await textService.WriteLineAsync(userpath, "this is a test");
    }
    
    

    Non dimenticare di passare userpath a un nome file e un percorso che ha senso nel computer.

  2. Compilare ed eseguire il codice. Quando viene visualizzata l'istanza sperimentale di Visual Studio, aprire una soluzione. In questo modo l'oggetto AsyncPackage viene ricaricato automaticamente. Quando l'inizializzatore è in esecuzione, si dovrebbe trovare un file nel percorso specificato.

Usare un servizio asincrono in un gestore comandi

Ecco un esempio di come usare un servizio asincrono in un comando di menu. È possibile usare la procedura illustrata qui per usare il servizio in altri metodi non asincroni.

  1. Aggiungere un comando di menu al progetto. (In Esplora soluzioni, selezionare il nodo del progetto, fare clic con il pulsante destro del mouse e selezionare Aggiungi>nuovo comando personalizzato di estendibilità>elemento.> Denominare il file di comando TestAsyncCommand.cs.

  2. Il modello di comando personalizzato aggiunge nuovamente il Initialize() metodo al file TestAsyncPackage.cs per inizializzare il comando. Initialize() Nel metodo copiare la riga che inizializza il comando. La cartella dovrebbe avere un aspetto simile a questo:

    TestAsyncCommand.Initialize(this);
    

    Spostare questa riga nel InitializeAsync() metodo nel file AsyncPackageForService.cs . Poiché si tratta di un'inizializzazione asincrona, è necessario passare al thread principale prima di inizializzare il comando usando SwitchToMainThreadAsync. L'aspetto dovrebbe risultare simile al seguente:

    
    protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await base.InitializeAsync(cancellationToken, progress);
        this.AddService(typeof(STextWriterService), CreateTextWriterService);
    
        ITextWriterService textService =
           await this.GetServiceAsync(typeof(STextWriterService)) as ITextWriterService;
    
        string userpath = @"C:\MyDir\MyFile.txt";
        await textService.WriteLineAsync(userpath, "this is a test");
    
        await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
        TestAsyncCommand.Initialize(this);
    }
    
    
  3. Eliminare il Initialize() metodo .

  4. Nel file TestAsyncCommand.cs trovare il MenuItemCallback() metodo . Eliminare il corpo del metodo .

  5. Aggiungere una direttiva using:

    using System.IO;
    
  6. Aggiungere un metodo asincrono denominato UseTextWriterAsync(), che ottiene il servizio e usa i relativi metodi:

    private async System.Threading.Tasks.Task UseTextWriterAsync()
    {
        // Query text writer service asynchronously to avoid a blocking call.
        ITextWriterService textService =
           await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(STextWriterService))
              as ITextWriterService;
    
        string userpath = @"C:\MyDir\MyFile.txt";
        await textService.WriteLineAsync(userpath, "this is a test");
       }
    
    
  7. Chiamare questo metodo dal MenuItemCallback() metodo :

    private void MenuItemCallback(object sender, EventArgs e)
    {
        UseTextWriterAsync();
    }
    
    
  8. Creare la soluzione e avviare il debug. Quando viene visualizzata l'istanza sperimentale di Visual Studio, passare al menu Strumenti e cercare la voce di menu Invoke TestAsyncCommand . Quando si fa clic su di esso, TextWriterService scrive nel file specificato. Non è necessario aprire una soluzione, perché richiamare il comando fa anche in modo che il pacchetto sia caricato.