Použití AsyncPackage k načtení balíčků VSPackage na pozadí

Načtení a inicializace balíčku VS může mít za následek vstupně-výstupní operace disku. Pokud k takovým vstupně-výstupním operacím dojde ve vlákně uživatelského rozhraní, může to vést k problémům s odezvou. V sadě Visual Studio 2015 jsme zavedli AsyncPackage třídu, která umožňuje načítání balíčků do vlákna na pozadí.

Vytvoření balíčku AsyncPackage

Můžete začít vytvořením projektu VSIX (souborový>nový>projekt>Visual C#>Rozšiřitelnost>projektu VSIX) a přidáním balíčku VSPackage do projektu (klikněte pravým tlačítkem myši na projekt a přidejte>položku>Rozšiřitelnost> položky>jazyka C#). Pak můžete vytvořit služby a přidat tyto služby do balíčku.

  1. Odvodit balíček z AsyncPackage.

  2. Pokud poskytujete služby, jejichž dotazování může způsobit načtení balíčku:

    Chcete-li označit visual Studio, že váš balíček je bezpečný pro načítání na pozadí a vyjádřit se k tomuto chování, měli byste PackageRegistrationAttribute nastavit Vlastnost AllowsBackgroundLoading na true v konstruktoru atributů.

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

    Chcete-li indikovat v sadě Visual Studio, že je bezpečné vytvořit instanci služby ve vlákně na pozadí, měli byste nastavit IsAsyncQueryable vlastnost na hodnotu true v konstruktoru ProvideServiceAttribute .

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. Pokud načítáte kontexty uživatelského rozhraní, měli byste zadat PackageAutoLoadFlags.BackgroundLoad pro ProvideAutoLoadAttribute hodnotu OR (0x2) do příznaků zapsaných jako hodnotu položky automatického načítání balíčku.

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. Pokud máte k dispozici asynchronní inicializaci, měli byste přepsat InitializeAsync. Odeberte metodu Initialize() poskytnutou šablonou VSIX. (Metoda Initialize() v AsyncPackage je zapečetěná. K přidání asynchronních služeb do balíčku můžete použít některou AddService z metod.

    POZNÁMKA: Volání base.InitializeAsync()můžete změnit zdrojový kód na:

    await base.InitializeAsync(cancellationToken, progress);
    
  5. Musíte se postarat o to, aby rpcs (vzdálené volání procedur) z asynchronního inicializačního kódu (v InitializeAsync). K těmto událostem může dojít, když voláte GetService přímo nebo nepřímo. Pokud se vyžaduje načtení synchronizace, vlákno uživatelského rozhraní bude blokovat použití JoinableTaskFactory. Výchozí blokující model zakáže rpcs. To znamená, že pokud se pokusíte použít RPC z asynchronních úloh, dojde k zablokování, pokud vlákno uživatelského rozhraní samotné čeká na načtení balíčku. Obecnou alternativou je zařazování kódu do vlákna uživatelského rozhraní v případě potřeby pomocí něčeho, jako je joinable Task FactorySwitchToMainThreadAsync nebo nějaký jiný mechanismus, který nepoužívá RPC. Nepoužívejte ThreadHelper.Generic.Invoke nebo obecně blokujte volající vlákno čekající na přístup do vlákna uživatelského rozhraní.

    POZNÁMKA: Ve své InitializeAsync metodě byste se měli vyhnout použití GetService nebo QueryService. Pokud je budete muset použít, musíte nejprve přepnout na vlákno uživatelského rozhraní. Alternativou je použití GetServiceAsync z AsyncPackage (přetypováním na IAsyncServiceProvider.)

    C#: Vytvoření balíčku 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);
    }
}

Převod existujícího balíčku VSPackage na AsyncPackage

Většina práce je stejná jako vytvoření nového balíčku AsyncPackage. Postupujte podle kroků 1 až 5 výše. Je také potřeba věnovat zvláštní opatrnosti s následujícími doporučeními:

  1. Nezapomeňte odebrat přepsání Initialize , které jste měli v balíčku.

  2. Vyhněte se zablokování: V kódu můžou být skryté rpcs. který se teď děje ve vlákně na pozadí. Ujistěte se, že pokud vytváříte RPC (například GetService), musíte buď (1) přepnout na hlavní vlákno, nebo (2) použít asynchronní verzi rozhraní API, pokud existuje (například GetServiceAsync).

  3. Nepřepínejte mezi vlákny příliš často. Pokuste se lokalizovat práci, ke které může dojít ve vlákně na pozadí, aby se zkrátila doba načítání.

Dotazování služeb z AsyncPackage

AsyncPackage může nebo nemusí načítat asynchronně v závislosti na volajícím. Příklad:

  • Pokud volající volal GetService nebo QueryService (synchronní rozhraní API) nebo

  • Pokud volající volal IVsShell::LoadPackage (nebo IVsShell5::LoadPackageWithContext) nebo

  • Zatížení se aktivuje kontextem uživatelského rozhraní, ale nezadali jste mechanismus kontextu uživatelského rozhraní, který můžete načíst asynchronně.

    pak se balíček načte synchronně.

    Váš balíček má stále příležitost (ve fázi asynchronní inicializace) provést práci s vláknem uživatelského rozhraní, i když vlákno uživatelského rozhraní bude zablokováno pro dokončení této práce. Pokud volající používá IAsyncServiceProvider k asynchronnímu dotazování pro vaši službu, pak se načtení a inicializace provede asynchronně za předpokladu, že neblokují okamžitě u výsledného objektu úlohy.

    C#: Asynchronní dotazování služby:

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;