使用 AsyncPackage 在后台加载 VSPackage

加载和初始化 VS 包可能会导致磁盘 I/O。 如果在 UI 线程上发生此类 I/O,可能会导致响应问题。 为了解决此问题,Visual Studio 2015 引入了 AsyncPackage 在后台线程上启用包加载的类。

创建 AsyncPackage

首先,可以创建 VSIX 项目(文件>新建>项目>Visual C#>Extensibility>VSIX 项目)并将 VSPackage 添加到项目(右键单击项目并添加新>>C# 项>扩展性>Visual Studio 包)。 然后,可以创建服务并将这些服务添加到包。

  1. AsyncPackage中派生包。

  2. 如果你正在提供服务,其查询可能会导致包加载:

    若要向 Visual Studio 指示包对于后台加载是安全的,并且要选择加入此行为,PackageRegistrationAttribute应在属性构造函数中将 AllowsBackgroundLoading 属性设置为 true。

    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
    
    

    若要向 Visual Studio 指示在后台线程上实例化服务是安全的,应在构造函数中ProvideServiceAttributeIsAsyncQueryable属性设置为 true。

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. 如果要通过 UI 上下文加载,则应将 OR 值 (0x2) 的 ProvideAutoLoadAttribute PackageAutoLoadFlags.BackgroundLoad 指定为作为包自动加载项的值写入的标志中。

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. 如果有异步初始化工作要做,则应重写 InitializeAsyncInitialize()删除 VSIX 模板提供的方法。 Initialize()(AsyncPackage的方法是密封的)。 可以使用任何 AddService 方法将异步服务添加到包。

    注意:若要调用 base.InitializeAsync(),可以将源代码更改为:

    await base.InitializeAsync(cancellationToken, progress);
    
  5. 必须注意不要从异步初始化代码(在 InitializeAsync)发出 RPC(远程过程调用)。 在直接或间接调用 GetService 时,可能会出现这些情况。 当需要同步加载时,UI 线程将阻止使用 JoinableTaskFactory。 默认阻止模型禁用 RPC。 这意味着,如果尝试从异步任务使用 RPC,则如果 UI 线程本身正在等待包加载,则会死锁。 一般替代方法是根据需要使用联接任务工厂SwitchToMainThreadAsync或其他不使用 RPC 的机制将代码封送给 UI 线程。 请勿使用 ThreadHelper.Generic.Invoke 或通常阻止正在等待访问 UI 线程的调用线程。

    注意:应避免在方法中使用 InitializeAsync GetServiceQueryService。 如果必须使用这些资源,则需要先切换到 UI 线程。 替代方法是从 AsyncPackage 使用GetServiceAsync(通过将它强制转换为 IAsyncServiceProvider.)

    C#:创建 AsyncPackage:

[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
public sealed class TestPackage : AsyncPackage
{
    protected override Task InitializeAsync(System.Threading.CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        this.AddService(typeof(SMyTestService), CreateService, true);
        return Task.FromResult<object>(null);
    }
}

将现有 VSPackage 转换为 AsyncPackage

大多数工作都与创建新的 AsyncPackage 相同。 按照上述步骤 1 到 5 进行操作。 还需要注意以下建议:

  1. 请记住删除包中已有的 Initialize 替代。

  2. 避免死锁:代码中可能存在隐藏的 RPC。 现在发生在后台线程上。 确保如果要创建 RPC(例如 GetService),则需要(1)切换到主线程,或者(2)使用 API 的异步版本(例如 GetServiceAsync)。

  3. 不要过于频繁地在线程之间切换。 尝试本地化后台线程中可能发生的工作以减少加载时间。

从 AsyncPackage 查询服务

AsyncPackage 可能或可能不会异步加载,具体取决于调用方。 例如,

  • 如果调用方称为 GetServiceQueryService (两个同步 API) 或

  • 如果调用方称为 IVsShell::LoadPackage (或 IVsShell5::LoadPackageWithContext) 或

  • 负载由 UI 上下文触发,但未指定 UI 上下文机制可以异步加载

    然后,包将同步加载。

    你的包仍有机会(在其异步初始化阶段)在 UI 线程下执行工作,尽管该工作的完成将阻止 UI 线程。 如果调用方使用 IAsyncServiceProvider 异步查询服务,则加载和初始化将以异步方式完成,假定它们不会立即阻止生成的任务对象。

    C#:如何异步查询服务:

using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

IAsyncServiceProvider asyncServiceProvider = Package.GetService(typeof(SAsyncServiceProvider)) as IAsyncServiceProvider;
IMyTestService testService = await asyncServiceProvider.GetServiceAsync(typeof(SMyTestService)) as IMyTestService;