提供异步 Visual Studio 服务
如果要获取服务而不阻止 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 会写入指定的文件。 (无需打开解决方案,因为调用命令也会导致包加载。