共用方式為


在背景中使用 AsyncPackage 載入 VSPackage

載入和初始化 VS 套件可能會導致磁碟 I/O。 如果這類 I/O 發生在 UI 執行緒上,可能會導致回應性問題。 為了解決這個問題,Visual Studio 2015 引進了可在背景執行緒上載入套件的 AsyncPackage 類別。

建立 AsyncPackage

首先,您可以建立 VSIX 專案 ([檔案]>[新增]>[專案]>[Visual C#]>[擴充性]>[VSIX 專案]),並將 VSPackage 新增至專案 (以滑鼠右鍵按一下專案,然後按一下 [新增]>[新增項目]>[C# 項目]>[擴充性]>[Visual Studio 套件])。 然後,您可以建立服務,並將這些服務新增至您的套件。

  1. AsyncPackage 衍生套件。

  2. 如果您要提供服務,其查詢可能會導致您的套件載入:

    若要向 Visual Studio 指出您的套件對於背景載入是安全的,並且選擇加入此行為,您的 PackageRegistrationAttribute 應該在屬性建構函式中將 AllowsBackgroundLoading 屬性設定為 true。

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

    若要向 Visual Studio 指出在背景執行緒上具現化服務是安全的,您應該在 ProvideServiceAttribute 建構函式中將 IsAsyncQueryable 屬性設定為 true。

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. 如果您要透過UI內容載入,則應針對 ProvideAutoLoadAttribute 指定 PackageAutoLoadFlags.BackgroundLoad,或者將進入旗標中的值 (0x2) 寫入為您套件自動載入項目的值。

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. 如果您有非同步初始化工作要處理,您應該覆寫 InitializeAsync。 移除 VSIX 範本所提供的 Initialize() 方法。 (AsyncPackage 中的 Initialize() 方法已密封)。 您可以使用任何 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;