Verwenden von AsyncPackage zum Laden von VSPackages im Hintergrund

Das Laden und Initialisieren eines VS-Pakets kann zu Datenträger-E/A führen. Wenn solche E/A-Vorgänge im UI-Thread auftreten, kann dies zu Reaktionsproblemen führen. Um dies zu beheben, hat Visual Studio 2015 die AsyncPackage Klasse eingeführt, die das Laden von Paketen in einem Hintergrundthread ermöglicht.

Erstellen eines AsyncPackage

Sie können beginnen, indem Sie ein VSIX-Projekt (File>New>Project>Visual C#>Extensibility>VSIX Project) erstellen und dem Projekt ein VSPackage hinzufügen (klicken Sie mit der rechten Maustaste auf das Projekt, und fügen Sie>das Visual Studio-Elementerweiterungspaket> für neues Element>>C# hinzu). Anschließend können Sie Ihre Dienste erstellen und diese Dienste zu Ihrem Paket hinzufügen.

  1. Leiten Sie das Paket von AsyncPackage.

  2. Wenn Sie Dienste bereitstellen, deren Abfrage dazu führen kann, dass Ihr Paket geladen wird:

    Um Visual Studio anzugeben, dass Ihr Paket für das Laden im Hintergrund sicher ist und sich für dieses Verhalten entscheiden soll, sollten Sie PackageRegistrationAttribute im Attributkonstruktor die Eigenschaft "AllowsBackgroundLoading" auf "true" festlegen.

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

    Um Visual Studio anzugeben, dass es sicher ist, Ihren Dienst in einem Hintergrundthread zu instanziieren, sollten Sie die IsAsyncQueryable Eigenschaft im ProvideServiceAttribute Konstruktor auf "true" festlegen.

    [ProvideService(typeof(SMyTestService), IsAsyncQueryable = true)]
    
    
  3. Wenn Sie über UI-Kontexte laden, sollten Sie PackageAutoLoadFlags.BackgroundLoad für den ProvideAutoLoadAttribute OR -Wert (0x2) in die Flags angeben, die als Wert des automatischen Ladeeintrags Ihres Pakets geschrieben wurden.

    [ProvideAutoLoad(UIContextGuid, PackageAutoLoadFlags.BackgroundLoad)]
    
    
  4. Wenn Die asynchrone Initialisierung funktioniert, sollten Sie dies außer Kraft setzen InitializeAsync. Entfernen Sie die Initialize() von der VSIX-Vorlage bereitgestellte Methode. (Die Initialize() Methode in AsyncPackage ist versiegelt). Sie können eine der AddService Methoden verwenden, um Ihrem Paket asynchrone Dienste hinzuzufügen.

    HINWEIS: Zum Aufrufen base.InitializeAsync()können Sie den Quellcode in Folgendes ändern:

    await base.InitializeAsync(cancellationToken, progress);
    
  5. Sie müssen sicherstellen, dass NICHT RPCs (Remote Procedure Call) aus Dem asynchronen Initialisierungscode (in InitializeAsync) ausgeführt werden. Diese können auftreten, wenn Sie direkt oder indirekt anrufen GetService . Wenn Synchronisierungsladevorgänge erforderlich sind, blockiert der UI-Thread die Verwendung JoinableTaskFactory. Das Standardblockierungsmodell deaktiviert RPCs. Dies bedeutet, dass Sie, wenn Sie versuchen, ein RPC aus Ihren asynchronen Aufgaben zu verwenden, deadlocken, wenn der UI-Thread selbst auf das Laden des Pakets wartet. Die allgemeine Alternative besteht darin, den Code bei Bedarf mithilfe von Joinable Task FactorysSwitchToMainThreadAsync oder einem anderen Mechanismus, der kein RPC verwendet, an den UI-Thread zu marshallen. Verwenden Sie ThreadHelper.Generic.Invoke nicht, oder blockieren Sie den aufrufenden Thread, der auf den Ui-Thread wartet.

    HINWEIS: Sie sollten die Verwendung von GetService oder QueryService in Ihrer InitializeAsync Methode vermeiden. Wenn Sie diese verwenden müssen, müssen Sie zuerst zum UI-Thread wechseln. Die Alternative besteht darin, von Ihrem AsyncPackage zu verwenden GetServiceAsync (indem Sie es in IAsyncServiceProvider.) umwandeln.

    C#: Erstellen eines 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);
    }
}

Konvertieren eines vorhandenen VSPackage in AsyncPackage

Der Großteil der Arbeit entspricht dem Erstellen eines neuen AsyncPackage. Führen Sie die oben beschriebenen Schritte 1 bis 5 aus. Sie müssen auch mit den folgenden Empfehlungen zusätzliche Vorsicht walten lassen:

  1. Denken Sie daran, die Außerkraftsetzung zu entfernen, die Initialize Sie in Ihrem Paket hatten.

  2. Vermeiden Sie Deadlocks: Es könnten ausgeblendete RPCs in Ihrem Code vorhanden sein. Dies geschieht jetzt in einem Hintergrundthread. Stellen Sie sicher, dass Sie beim Erstellen eines RPC (z. B. GetService) entweder (1) zum Standard Thread wechseln oder (2) die asynchrone Version der API verwenden müssen, wenn eine vorhanden ist (z. B. GetServiceAsync).

  3. Wechseln Sie nicht zu häufig zwischen Threads. Versuchen Sie, die Arbeit zu lokalisieren, die in einem Hintergrundthread auftreten kann, um die Ladezeit zu verringern.

Abfragen von Diensten von AsyncPackage

Ein AsyncPackage kann je nach Aufrufer asynchron geladen werden. Beispiel:

  • Wenn der Aufrufer "GetService" oder "QueryService" (beide synchrone APIs) oder

  • Wenn der Aufrufer IVsShell::LoadPackage (oder IVsShell5::LoadPackageWithContext) oder

  • Die Last wird durch einen Ui-Kontext ausgelöst, aber Sie haben nicht angegeben, dass der Ui-Kontextmechanismus Sie asynchron laden kann.

    dann wird ihr Paket synchron geladen.

    Ihr Paket hat weiterhin die Möglichkeit (in der asynchronen Initialisierungsphase), den UI-Thread zu deaktivieren, obwohl der UI-Thread für den Abschluss dieser Arbeit blockiert wird. Wenn der Aufrufer IAsyncServiceProvider zum asynchronen Abfragen des Diensts verwendet, erfolgt die Auslastung und Initialisierung asynchron, vorausgesetzt, sie blockieren nicht sofort das resultierende Aufgabenobjekt.

    C#: Asynchrones Abfragen des Diensts:

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;