提供异步 Visual Studio 服务

如果要获取服务而不阻止 UI 线程,则应创建异步服务并在后台线程上加载包。 为此,可以使用而不是 a AsyncPackage Package,并使用异步包的特殊异步方法添加服务。

有关提供同步 Visual Studio 服务的信息,请参阅 如何:提供服务

实现异步服务

  1. 创建 VSIX 项目(文件>新建>项目>Visual C#>Extensiblity>VSIX 项目)。 将项目 命名为 TestAsync

  2. 将 VSPackage 添加到项目。 选择解决方案资源管理器中的项目节点,然后单击“添加新>>Visual C# 项>扩展性>Visual Studio 包”。 将此文件 命名为 TestAsyncPackage.cs

  3. TestAsyncPackage.cs 中,将包更改为从中继承, AsyncPackage 而不是 Package

    public sealed class TestAsyncPackage : AsyncPackage
    
  4. 若要实现服务,需要创建三种类型:

    • 标识服务的接口。 其中许多接口都是空的,也就是说,它们没有方法,因为它们仅用于查询服务。

    • 描述服务接口的接口。 此接口包括要实现的方法。

    • 实现服务和服务接口的类。

  5. 下面的示例演示了三种类型的基本实现。 服务类的构造函数必须设置服务提供程序。 在此示例中,我们将只将服务添加到包代码文件中。

  6. 将以下 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;
    
  7. 下面是异步服务实现。 请注意,需要在构造函数中设置异步服务提供程序,而不是同步服务提供商:

    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 提供服务的包。 与注册同步服务不同,必须确保包和服务都支持异步加载:

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

添加服务

  1. 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);

  2. 添加对 Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll 的引用。

  3. 将回调方法实现为创建和返回服务的异步方法。

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

使用服务

现在,你可以获取该服务并使用其方法。

  1. 我们将在初始值设定项中显示此内容,但你可以在你想要使用该服务的任何位置获取服务。

    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 计算机上有意义的文件名和路径!

  2. 生成并运行代码。 当 Visual Studio 的实验实例出现时,打开解决方案。 这会导致 AsyncPackage 自动加载。 当初始值设定项运行时,应在指定的位置中找到文件。

在命令处理程序中使用异步服务

下面是有关如何在菜单命令中使用异步服务的示例。 可以使用此处显示的过程在其他非异步方法中使用服务。

  1. 向项目添加菜单命令。 (在解决方案资源管理器,选择项目节点,右键单击,然后选择“添加新>项>扩展性>自定义命令”。将命令文件命名为 TestAsyncCommand.cs

  2. 自定义命令模板将该方法重新添加到 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);
    }
    
    
  3. Initialize()删除方法。

  4. TestAsyncCommand.cs 文件中,找到该方法 MenuItemCallback() 。 删除方法的正文。

  5. 添加 using 指令:

    using System.IO;
    
  6. 添加名为 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");
       }
    
    
  7. MenuItemCallback() 方法调用此方法:

    private void MenuItemCallback(object sender, EventArgs e)
    {
        UseTextWriterAsync();
    }
    
    
  8. 生成解决方案并开始调试。 当 Visual Studio 的实验实例出现时,转到 “工具” 菜单并查找 “调用 TestAsyncCommand ”菜单项。 单击它时,TextWriterService 会写入指定的文件。 (无需打开解决方案,因为调用命令也会导致包加载。