如果要获取服务而不阻止 UI 线程,则应创建异步服务并在后台线程上加载包。 为此,可以使用而不是 a AsyncPackage Package,并使用异步包的特殊异步方法添加服务。
有关提供同步 Visual Studio 服务的信息,请参阅 如何:提供服务。
实现异步服务
- 创建 VSIX 项目(文件>新建>项目>Visual C#>Extensiblity>VSIX 项目)。 将项目 命名为 TestAsync。 
- 将 VSPackage 添加到项目。 选择解决方案资源管理器中的项目节点,然后单击“添加新>项>Visual C# 项>扩展性>Visual Studio 包”。 将此文件 命名为 TestAsyncPackage.cs。 
- 在 TestAsyncPackage.cs 中,将包更改为从中继承, - AsyncPackage而不是- Package:- public sealed class TestAsyncPackage : AsyncPackage
- 若要实现服务,需要创建三种类型: - 标识服务的接口。 其中许多接口都是空的,也就是说,它们没有方法,因为它们仅用于查询服务。 
- 描述服务接口的接口。 此接口包括要实现的方法。 
- 实现服务和服务接口的类。 
 
- 下面的示例演示了三种类型的基本实现。 服务类的构造函数必须设置服务提供程序。 在此示例中,我们将只将服务添加到包代码文件中。 
- 将以下 using 指令添加到包文件: - 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;
- 下面是异步服务实现。 请注意,需要在构造函数中设置异步服务提供程序,而不是同步服务提供商: - 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); }
注册服务
若要注册服务,请将该服务添加到 ProvideServiceAttribute 提供服务的包。 与注册同步服务不同,必须确保包和服务都支持异步加载:
- 必须将 AllowsBackgroundLoading = true 字段添加到PackageRegistrationAttribute该字段,以确保包可以异步初始化。有关 PackageRegistrationAttribute 的详细信息,请参阅“注册和注销 VSPackage”。 
- 必须将 IsAsyncQueryable = true 字段添加到ProvideServiceAttribute该字段,以确保可以异步初始化服务实例。 - 下面是使用异步服务注册的示例 - AsyncPackage:
[ProvideService((typeof(STextWriterService)), IsAsyncQueryable = true)]
[ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid(TestAsyncPackage.PackageGuidString)]
public sealed class TestAsyncPackage : AsyncPackage
{. . . }
添加服务
- 在 TestAsyncPackage.cs 中,删除 - Initialize()该方法并重写- InitializeAsync()该方法。 添加服务,并添加回调方法以创建服务。 下面是添加服务的异步初始值设定项的示例:- protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) { await base.InitializeAsync(cancellationToken, progress); this.AddService(typeof(STextWriterService), CreateTextWriterService); }- 若要使此服务在此包外部可见,请将提升标志值设置为 true 作为最后一个参数: - this.AddService(typeof(STextWriterService), CreateTextWriterService, true);
- 添加对 Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll 的引用。 
- 将回调方法实现为创建和返回服务的异步方法。 - public async Task<object> CreateTextWriterService(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType) { TextWriterService service = new TextWriterService(this); await service.InitializeAsync(cancellationToken); return 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"); }- 不要忘记更改 - userpath计算机上有意义的文件名和路径!
- 生成并运行代码。 当 Visual Studio 的实验实例出现时,打开解决方案。 这会导致 - AsyncPackage自动加载。 当初始值设定项运行时,应在指定的位置中找到文件。
在命令处理程序中使用异步服务
下面是有关如何在菜单命令中使用异步服务的示例。 可以使用此处显示的过程在其他非异步方法中使用服务。
- 向项目添加菜单命令。 (在解决方案资源管理器,选择项目节点,右键单击,然后选择“添加新>项>扩展性>自定义命令”。将命令文件命名为 TestAsyncCommand.cs。 
- 自定义命令模板将该方法重新添加到 - Initialize()TestAsyncPackage.cs 文件中,以便初始化命令。 在方法中- Initialize(),复制初始化命令的行。 应如下所示:- TestAsyncCommand.Initialize(this);- 将此行移动到 - InitializeAsync()AsyncPackageForService.cs 文件中的方法。 由于这是异步初始化,因此在使用命令 SwitchToMainThreadAsync初始化之前,必须切换到主线程。 它现在应如下所示:- 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); }
- Initialize()删除方法。
- 在 TestAsyncCommand.cs 文件中,找到该方法 - MenuItemCallback()。 删除方法的正文。
- 添加 using 指令: - using System.IO;
- 添加名为 - UseTextWriterAsync()的异步方法,该方法获取服务并使用其方法:- 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"); }
- 从 - MenuItemCallback()方法调用此方法:- private void MenuItemCallback(object sender, EventArgs e) { UseTextWriterAsync(); }
- 生成解决方案并开始调试。 当 Visual Studio 的实验实例出现时,转到 “工具” 菜单并查找 “调用 TestAsyncCommand ”菜单项。 单击它时,TextWriterService 会写入指定的文件。 (无需打开解决方案,因为调用命令也会导致包加载。