Fournir un service Visual Studio asynchrone
Si vous souhaitez obtenir un service sans bloquer le thread d’interface utilisateur, vous devez créer un service asynchrone et charger le package sur un thread d’arrière-plan. À cet effet, vous pouvez utiliser un AsyncPackage service plutôt qu’un Package, et ajouter le service avec les méthodes asynchrones spéciales du package asynchrone.
Pour plus d’informations sur la fourniture de services Visual Studio synchrones, consultez Guide pratique pour fournir un service.
Implémenter un service asynchrone
Créez un projet VSIX (Fichier>Nouveau>projet>Visual C#>Extensiblity>VSIX Project). Nommez le projet TestAsync.
Ajoutez un VSPackage au projet. Sélectionnez le nœud du projet dans l’Explorateur de solutions, puis cliquez sur Ajouter un>nouvel élément>Visual C# Élément>extensibility>Visual Studio Package. Nommez ce fichier TestAsyncPackage.cs.
Dans TestAsyncPackage.cs, modifiez le package pour hériter plutôt
AsyncPackage
quePackage
:public sealed class TestAsyncPackage : AsyncPackage
Pour implémenter un service, vous devez créer trois types :
Interface qui identifie le service. La plupart de ces interfaces sont vides, c’est-à-dire qu’elles n’ont aucune méthode, car elles ne sont utilisées que pour interroger le service.
Interface qui décrit l’interface de service. Cette interface inclut les méthodes à implémenter.
Classe qui implémente le service et l’interface de service.
L’exemple suivant montre une implémentation très simple des trois types. Le constructeur de la classe de service doit définir le fournisseur de services. Dans cet exemple, nous allons simplement ajouter le service au fichier de code du package.
Ajoutez les directives using suivantes au fichier de package :
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;
Voici l’implémentation asynchrone du service. Notez que vous devez définir le fournisseur de services asynchrone plutôt que le fournisseur de services synchrone dans le constructeur :
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); }
Inscrire un service
Pour inscrire un service, ajoutez le ProvideServiceAttribute package qui fournit le service. Différent de l’inscription d’un service synchrone, vous devez vous assurer que le package et le service prennent en charge le chargement asynchrone :
Vous devez ajouter le champ AllowsBackgroundLoading = true au package pour vous assurer que le PackageRegistrationAttribute package peut être initialisé de manière asynchrone Pour plus d’informations sur PackageRegistrationAttribute, consultez Register and unregister VSPackages.
Vous devez ajouter le champ IsAsyncQueryable = true à l’instance de ProvideServiceAttribute service pour vous assurer que l’instance de service peut être initialisée de manière asynchrone.
Voici un exemple d’inscription de
AsyncPackage
service asynchrone :
[ProvideService((typeof(STextWriterService)), IsAsyncQueryable = true)]
[ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid(TestAsyncPackage.PackageGuidString)]
public sealed class TestAsyncPackage : AsyncPackage
{. . . }
Ajouter un service
Dans TestAsyncPackage.cs, supprimez la
Initialize()
méthode et remplacez laInitializeAsync()
méthode. Ajoutez le service et ajoutez une méthode de rappel pour créer les services. Voici un exemple d’initialiseur asynchrone ajoutant un service :protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) { await base.InitializeAsync(cancellationToken, progress); this.AddService(typeof(STextWriterService), CreateTextWriterService); }
Pour rendre ce service visible en dehors de ce package, définissez la valeur de l’indicateur de promotion sur true comme dernier paramètre :
this.AddService(typeof(STextWriterService), CreateTextWriterService, true);
Ajoutez une référence à Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll.
Implémentez la méthode de rappel en tant que méthode asynchrone qui crée et retourne le service.
public async Task<object> CreateTextWriterService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType) { TextWriterService service = new TextWriterService(this); await service.InitializeAsync(cancellationToken); return service; }
Utiliser un service
Vous pouvez maintenant obtenir le service et utiliser ses méthodes.
Nous allons le montrer dans l’initialiseur, mais vous pouvez obtenir le service partout où vous souhaitez utiliser le service.
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"); }
N’oubliez pas de passer
userpath
à un nom de fichier et un chemin d’accès logiques sur votre ordinateur !Générez et exécutez le code. Lorsque l’instance expérimentale de Visual Studio s’affiche, ouvrez une solution. Cela entraîne le
AsyncPackage
chargement automatique. Une fois l’initialiseur exécuté, vous devez trouver un fichier à l’emplacement que vous avez spécifié.
Utiliser un service asynchrone dans un gestionnaire de commandes
Voici un exemple d’utilisation d’un service asynchrone dans une commande de menu. Vous pouvez utiliser la procédure présentée ici pour utiliser le service dans d’autres méthodes non asynchrones.
Ajoutez une commande de menu à votre projet. (Dans le Explorateur de solutions, sélectionnez le nœud du projet, cliquez avec le bouton droit, puis sélectionnez Ajouter une commande personnalisée d’extensibilité>>d’élément>.) Nommez le fichier de commande TestAsyncCommand.cs.
Le modèle de commande personnalisé ajoute à nouveau la
Initialize()
méthode au fichier TestAsyncPackage.cs afin d’initialiser la commande. Dans laInitialize()
méthode, copiez la ligne qui initialise la commande. Il doit se présenter comme suit :TestAsyncCommand.Initialize(this);
Déplacez cette ligne vers la
InitializeAsync()
méthode dans le fichier AsyncPackageForService.cs . Étant donné qu’il s’agit d’une initialisation asynchrone, vous devez basculer vers le thread principal avant d’initialiser la commande à l’aide SwitchToMainThreadAsyncde . Elle doit maintenant ressembler à ceci :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); }
Supprimez la
Initialize()
méthode.Dans le fichier TestAsyncCommand.cs , recherchez la
MenuItemCallback()
méthode. Supprimez le corps de la méthode.Ajoutez une directive using :
using System.IO;
Ajoutez une méthode asynchrone nommée
UseTextWriterAsync()
, qui obtient le service et utilise ses méthodes :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"); }
Appelez cette méthode à partir de la
MenuItemCallback()
méthode :private void MenuItemCallback(object sender, EventArgs e) { UseTextWriterAsync(); }
Générez la solution et commencez le débogage. Lorsque l’instance expérimentale de Visual Studio s’affiche, accédez au menu Outils et recherchez l’élément de menu Invoke TestAsyncCommand . Lorsque vous cliquez dessus, TextWriterService écrit dans le fichier que vous avez spécifié. (Vous n’avez pas besoin d’ouvrir une solution, car l’appel de la commande entraîne également le chargement du package.)