Využívání zprostředkované služby

Tento dokument popisuje všechny kódy, vzory a upozornění související s pořízením, obecným použitím a vyřazením jakékoli zprostředkované služby. Pokud se chcete naučit používat konkrétní zprostředkovanou službu po získání, vyhledejte konkrétní dokumentaci pro zprostředkovanou službu.

S veškerým kódem v tomto dokumentu se důrazně doporučuje aktivovat funkci odkazových typů s možnou hodnotou null jazyka C#.

Načtení IServiceBrokeru

Chcete-li získat zprostředkovanou službu, musíte mít nejprve instanci IServiceBroker. Pokud je váš kód spuštěný v kontextu MEF (Managed Extensibility Framework) nebo VSPackage, obvykle chcete globálního zprostředkovatele služeb.

Zprostředkované služby by měly používat IServiceBroker to, které jsou přiřazeny při vyvolání služby.

Globální zprostředkovatel služeb

Visual Studio nabízí dva způsoby získání globálního zprostředkovatele služeb.

Použít GlobalProvider.GetServiceAsync k vyžádání SVsBrokeredServiceContainer:

IBrokeredServiceContainer container = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
IServiceBroker serviceBroker = container.GetFullAccessServiceBroker();

Od sady Visual Studio 2022 může kód spuštěný v aktivovaném rozšíření MEF importovat globálního zprostředkovatele služeb:

[Import(typeof(SVsFullAccessServiceBroker))]
IServiceBroker ServiceBroker { get; set; }

Všimněte si argumentu typeof atributu Import, který je povinný.

Každý požadavek pro globální IServiceBroker vytvoří novou instanci objektu, který slouží jako zobrazení globálního kontejneru zprostředkované služby. Tato jedinečná instance zprostředkovatele služeb umožňuje klientovi přijímat AvailabilityChanged události jedinečné pro použití daného klienta. Doporučujeme, aby každý klient nebo třída ve vašem rozšíření získaly vlastního zprostředkovatele služeb pomocí některého z výše uvedených přístupů, a ne získání jedné instance a její sdílení napříč celým rozšířením. Tento model také podporuje zabezpečené vzory kódování, ve kterých by zprostředkovaná služba neměla používat globálního zprostředkovatele služeb.

Důležité

IServiceBroker Implementace obvykle neimplementují IDisposable, ale tyto objekty nelze shromažďovat, zatímco AvailabilityChanged obslužné rutiny existují. Nezapomeňte vyvážit přidávání nebo odebírání obslužných rutin událostí, zejména pokud kód může během životnosti procesu zahodit zprostředkovatele služeb.

Kontextové zprostředkovatele služeb

Použití příslušného zprostředkovatele služeb je důležitým požadavkem modelu zabezpečení zprostředkovaných služeb, zejména v kontextu relací Live Share.

Zprostředkované služby jsou aktivovány vlastními IServiceBroker a měly by tuto instanci používat pro všechny své vlastní zprostředkované potřeby služeb, včetně služeb, které jsou nabíjené Proffer. Takový kód poskytuje BrokeredServiceFactory službu, která přijímá zprostředkovatele služeb, který bude používán instancí zprostředkované služby.

Načítání zprostředkovaného proxy služby

Načtení zprostředkované služby se obvykle provádí s metodou GetProxyAsync .

Metoda GetProxyAsync bude vyžadovat ServiceRpcDescriptor a rozhraní služby jako argument obecného typu. Dokumentace ke zprostředkované službě, kterou požadujete, by měla indikovat, kde získat popisovač a jaké rozhraní použít. U zprostředkovaných služeb zahrnutých v sadě Visual Studio by se rozhraní, které se má použít, mělo zobrazit v dokumentaci IntelliSense na popisovači. Zjistěte, jak najít popisovače pro zprostředkované služby sady Visual Studio ve zjišťování dostupných zprostředkovaných služeb.

IServiceBroker broker; // Acquired as described earlier in this topic
IMyService? myService = await broker.GetProxyAsync<IMyService>(serviceDescriptor, cancellationToken);
using (myService as IDisposable)
{
    Assumes.Present(myService); // Throw if service was not available
    await myService.SayHelloAsync();
}

Stejně jako u všech zprostředkovaných požadavků na službu aktivuje předchozí kód novou instanci zprostředkované služby. Po použití služby předchozí kód odstraní proxy server, protože spuštění ukončí using blok.

Důležité

Každý načtený proxy server musí být uvolněn, i když rozhraní služby není odvozeno z IDisposable. Odstranění je důležité, protože proxy často má zálohované vstupně-výstupní prostředky, které brání jeho uvolňování paměti. Vyřazení ukončí vstupně-výstupní operace, což proxy serveru umožní uvolnění paměti. K odstranění použijte podmíněné přetypování IDisposable a připravte se na přetypování, aby se zabránilo výjimce pro null proxy nebo proxy servery, které ve skutečnosti neimplementují IDisposable.

Nezapomeňte nainstalovat nejnovější balíček NuGet Microsoft.ServiceHub.Analyzers a nechat pravidla analyzátoru ISBxxxx povolená, aby se zabránilo takovým únikům .

Vyřazení proxy serveru má za následek vyřazení zprostředkované služby, která byla vyhrazena tomuto klientovi.

Pokud váš kód vyžaduje zprostředkovanou službu a nemůže dokončit svou práci, pokud služba není dostupná, může se uživateli zobrazit dialogové okno s chybou, pokud kód vlastní uživatelské prostředí a nevyvolá výjimku.

Cíle RPC klienta

Některé zprostředkované služby přijímají nebo vyžadují cíl vzdáleného volání procedur (RPC) pro zpětné volání volání. Taková možnost nebo požadavek by měly být v dokumentaci ke zprostředkované službě. Pro zprostředkované služby sady Visual Studio by tyto informace měly být zahrnuty do dokumentace IntelliSense popisovače.

V takovém případě může klient poskytnout jednu z těchto metod ServiceActivationOptions.ClientRpcTarget :

IMyService? myService = await broker.GetProxyAsync<IMyService>(
    serviceDescriptor,
    new ServiceActivationOptions
    {
        ClientRpcTarget = new MyCallbackObject(),
    },
    cancellationToken);

Vyvolání proxy klienta

Výsledkem požadavku zprostředkované služby je instance rozhraní služby implementované proxy serverem. Tento proxy server přesměruje volání a události každý směr, přičemž některé důležité rozdíly v chování od toho, co může očekávat při přímém volání služby.

Vzor pozorovatele

Pokud kontrakt služby přebírá parametry typu IObserver<T>, můžete se dozvědět více o tom, jak takový typ vytvořit v tématu Jak implementovat pozorovatele.

Lze ActionBlock<TInput> přizpůsobit implementaci IObserver<T> AsObserver pomocí metody rozšíření. System.Reactive.Observer třída z reactive framework je další alternativou k implementaci rozhraní sami.

Výjimky vyvolané z proxy serveru

  • Očekává RemoteInvocationException se, že bude vyvolána jakákoli výjimka vyvolaná zprostředkovanou službou. Původní výjimka se nachází v sadě InnerException. Toto je přirozené chování pro vzdáleně hostované služby, protože se jedná o chování z JsonRpc. Když je služba místní, místní proxy zabalí všechny výjimky stejným způsobem, aby kód klienta mohl mít pouze jednu cestu výjimky, která funguje pro místní a vzdálené služby.
    • ErrorCode Zkontrolujte vlastnost, pokud dokumentace služby naznačuje, že konkrétní kódy jsou nastavené na základě konkrétních podmínek, na kterých můžete větvet.
    • Širší sada chyb je oznámena zachytáváním RemoteRpcException, což je základní typ pro RemoteInvocationException.
  • Očekává se ConnectionLostException , že se vyvolá z jakéhokoli volání, když se připojení ke vzdálené službě ukončí nebo dojde k chybovému ukončení procesu hostujícího službu. Jedná se především o problém, kdy je možné službu získat vzdáleně.

Ukládání proxy serveru do mezipaměti

Při aktivaci zprostředkované služby a přidruženého proxy serveru dochází k nějakým výdajům, zejména pokud služba pochází ze vzdáleného procesu. Při častém použití zprostředkované služby zaručuje ukládání proxy serveru do mezipaměti napříč mnoha voláními do třídy, může být proxy server uložen v poli v dané třídě. Obsahující třída by měla být uvolnitelná a likvidovat proxy uvnitř své Dispose metody. Podívejte se na tento příklad:

class MyExtension : IDisposable
{
    readonly IServiceBroker serviceBroker;
    IMyService? serviceProxy;

    internal MyExtension(IServiceBroker serviceBroker)
    {
        this.serviceBroker = serviceBroker;
    }

    async Task SayHiAsync(CancellationToken cancellationToken)
    {
        if (this.serviceProxy is null)
        {
            this.serviceProxy = await this.serviceBroker.GetProxyAsync<IMyService>(serviceDescriptor, cancellationToken);
            Assumes.Present(this.serviceProxy);
        }

        await this.serviceProxy.SayHelloAsync();
    }

    public void Dispose()
    {
        (this.serviceProxy as IDisposable)?.Dispose();
    }
}

Předchozí kód je zhruba správný, ale nebere v úvahu podmínky časování mezi Dispose a SayHiAsync. Kód také nezohlední AvailabilityChanged události, které by měly vést k vyřazení proxy serveru, který byl dříve získán, a opětovnou žádost proxy serveru při příštím požadavku.

Třída ServiceBrokerClient je navržená tak, aby zpracovávala tyto podmínky časování a zneplatnění, aby vám pomohla udržet vlastní kód jednoduchý. Podívejte se na tento aktualizovaný příklad, který ukládá proxy server do mezipaměti pomocí této pomocné třídy:

class MyExtension : IDisposable
{
    readonly ServiceBrokerClient serviceBrokerClient;

    internal MyExtension(IServiceBroker serviceBroker)
    {
        this.serviceBrokerClient = new ServiceBrokerClient(serviceBroker);
    }

    async Task SayHiAsync(CancellationToken cancellationToken)
    {
        using var rental = await this.serviceBrokerClient.GetProxyAsync<IMyService>(descriptor, cancellationToken);
        Assumes.Present(rental.Proxy); // Throw if service is not available
        IMyService myService = rental.Proxy;
        await myService.SayHelloAsync();
    }

    public void Dispose()
    {
        // Disposing the ServiceBrokerClient will dispose of all proxies
        // when their rentals are released.
        this.serviceBrokerClient.Dispose();
    }
}

Předchozí kód je stále zodpovědný za odstranění ServiceBrokerClient a každého pronájmu proxy serveru. Konflikty časování mezi vyřazením a použitím proxy serveru zpracovává ServiceBrokerClient objekt, který odstraní každý proxy server uložený v mezipaměti v době vlastního odstranění nebo při posledním uvolnění pronájmu tohoto proxy serveru podle toho, co nastane naposledy.

Důležité upozornění týkající se ServiceBrokerClient

Volba mezi IServiceBroker a ServiceBrokerClient

Oba jsou uživatelsky přívětivé a výchozí by pravděpodobně měla být IServiceBroker.

Kategorie IServiceBroker ServiceBrokerClient
Uživatelsky přívětivé Ano Yes
Vyžaduje likvidaci. No Ano
Spravuje životnost proxy serveru. Ne. Vlastník musí po jeho použití odstranit proxy server. Ano, jsou stále aktivní a opakovaně se používají, pokud jsou platné.
Použitelné pro bezstavové služby Ano Yes
Použitelné pro stavové služby Yes No
Vhodné při přidání obslužných rutin událostí do proxy serveru Yes No
Událost upozorňovat na zneplatnění starého proxy serveru AvailabilityChanged Invalidated

ServiceBrokerClient poskytuje pohodlný způsob, jak rychle a často opakovaně používat proxy server, kde vás nezajímá, jestli se podkladová služba mezi operacemi nejvyšší úrovně změní mimo vás. Ale pokud se staráte o tyto věci a chcete spravovat životnost proxy sami, nebo potřebujete obslužné rutiny událostí (což znamená, že potřebujete spravovat životnost proxy), měli byste použít IServiceBroker.

Odolnost proti přerušení služeb

U zprostředkovaných služeb existuje několik druhů přerušení služeb:

Selhání aktivace zprostředkované služby

Když zprostředkovaná žádost o službu může být splněna dostupnou službou, ale služba factory vyvolá neošetřenou výjimku, vyvolá se zpět klientovi, ServiceActivationFailedException aby mohl pochopit a nahlásit selhání uživateli.

Pokud se zprostředkovaná žádost o službu nedá spárovat s žádnou dostupnou službou, null vrátí se klientovi. V takovém případě bude vyvolána, AvailabilityChanged kdy a pokud bude tato služba později dostupná.

Žádost o službu může být odmítnuta, protože služba tam není, ale protože nabízená verze je nižší než požadovaná verze. Náhradní plán může zahrnovat opakování žádosti o službu s nižšími verzemi, které váš klient zná a může s ním pracovat.

Pokud/když se zjeví latence ze všech kontrol neúspěšných verzí, klient může požádat o VisualStudioServices.VS2019_4Services.RemoteBrokeredServiceManifest, aby získal úplnou představu o tom, jaké služby a verze jsou dostupné ze vzdáleného zdroje.

Zpracování ukončených připojení

Proxy zprostředkované zprostředkované služby může selhat kvůli vyřazení připojení nebo chybovému ukončení procesu, který ho hostuje. Po takovém přerušení se ConnectionLostException vyvolá jakékoli volání na tomto proxy serveru.

Klient zprostředkované služby může proaktivně zjišťovat a reagovat na takové výpadky připojení tím, že zpracovává Disconnected událost. Aby bylo možné tuto událost dosáhnout, musí být proxy server přetypován k IJsonRpcClientProxy získání objektu JsonRpc . Toto přetypování by mělo být podmíněně provedeno tak, aby bylo možné řádně selhat, když je služba místní.

if (this.myService is IJsonRpcClientProxy clientProxy)
{
    clientProxy.JsonRpc.Disconnected += JsonRpc_Disconnected;
}

void JsonRpc_Disconnected(object? sender, JsonRpcDisconnectedEventArgs args)
{
    if (args.Reason == DisconnectedReason.RemotePartyTerminated)
    {
        // consider reacquisition of the service.
    }
}

Zpracování změn dostupnosti služeb

Klienti zprostředkované služby můžou dostávat oznámení o tom, kdy by se měli znovu dotazovat na zprostředkovanou službu, na kterou se dříve dotazovali zpracováním AvailabilityChanged události. Obslužné rutiny této události by se měly přidat před vyžádáním zprostředkované služby, aby se zajistilo, že událost vyvolaná brzy po provedení žádosti o službu neztratí kvůli konfliktu časování.

Pokud je zprostředkovaná služba požadována pouze po dobu trvání provádění jedné asynchronní metody, zpracování této události se nedoporučuje. Událost je nejrelevantní pro klienty, kteří ukládají proxy server po delší období, aby museli kompenzovat změny služeb a jsou v pozici k aktualizaci proxy serveru.

Tato událost může být vyvolána na libovolném vlákně, pravděpodobně souběžně na kód, který používá službu, kterou událost popisuje.

Několik změn stavu může vést k vyvolání této události, mezi které patří:

  • Řešení nebo složka, které se otevírají nebo zavírají.
  • Spustí se relace Live Share.
  • Dynamicky zaregistrovaná zprostředkovaná služba, která byla právě zjištěna.

Ovlivněná zprostředkovaná služba vede pouze k vyvolání této události klientům, kteří tuto službu dříve požadovali, ať už byla tato žádost splněna nebo ne.

Událost se vyvolá maximálně jednou na službu po každé žádosti o danou službu. Pokud například klient požaduje službu A a službu B , dojde ke změně dostupnosti, pro daného klienta se nevyvolá žádná událost. Později, když služba A dojde ke změně dostupnosti, klient obdrží událost. Pokud klient službu A znovu nepožádá, následné změny dostupnosti pro A nebudou mít za následek žádná další oznámení pro tohoto klienta. Jakmile klient znovu požádá O , může obdržet další oznámení týkající se této služby.

Událost se vyvolá, jakmile bude služba dostupná, už není k dispozici nebo dojde ke změně implementace, která vyžaduje, aby všichni předchozí klienti služby znovu dotazovali službu.

Zpracovává ServiceBrokerClient události změn dostupnosti týkající se proxy serverů uložených v mezipaměti automaticky tak, že vyhodí staré proxy servery, když se vrátí jakékoli pronájmy a požádá o novou instanci služby, když a pokud o jednu z nich požádá vlastník. Tato třída může podstatně zjednodušit kód, pokud je služba bezstavová a nevyžaduje, aby váš kód připojil obslužné rutiny událostí k proxy serveru.

Načtení zprostředkovaného kanálu služby

Přestože přístup ke zprostředkované službě prostřednictvím proxy je nejběžnější a nejpohodlnější technikou, může být v pokročilých scénářích vhodnější nebo nutné požádat o kanál této služby, aby klient mohl řídit rpc přímo nebo komunikovat jakýkoli jiný datový typ přímo.

Kanál do zprostředkované služby může být získán metodou GetPipeAsync . Tato metoda přebírá místo ServiceMoniker toho ServiceRpcDescriptor , že chování RPC poskytované popisovačem není povinné. Pokud máte popisovač, můžete z něj získat moniker prostřednictvím ServiceRpcDescriptor.Moniker vlastnosti.

I když jsou kanály vázané na vstupně-výstupní operace, nemají nárok na uvolňování paměti. Vyhněte se nevracení paměti tím, že tyto kanály vždy dokončíte, když už nebudou použity.

V následujícím fragmentu kódu se aktivuje zprostředkovaná služba a klient k ní má přímý kanál. Klient pak odešle obsah souboru do služby a odpojí se.

async Task SendMovieAsync(string movieFilePath, CancellationToken cancellationToken)
{
    IServiceBroker serviceBroker;
    IDuplexPipe? pipe = await serviceBroker.GetPipeAsync(serviceMoniker, cancellationToken);
    if (pipe is null)
    {
        throw new InvalidOperationException($"The brokered service '{serviceMoniker}' is not available.");
    }

    try
    {
        // Open the file optimized for async I/O
        using FileStream fs = new FileStream(movieFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true);
        await fs.CopyToAsync(pipe.Output.AsStream(), cancellationToken);
    }
    catch (Exception ex)
    {
        // Complete the pipe, passing through the exception so the remote side understands what went wrong.
        await pipe.Input.CompleteAsync(ex);
        await pipe.Output.CompleteAsync(ex);
        throw;
    }
    finally
    {
        // Always complete the pipe after successfully using the service.
        await pipe.Input.CompleteAsync();
        await pipe.Output.CompleteAsync();
    }
}

Testování zprostředkovaných klientů služeb

Zprostředkované služby jsou přiměřenou závislostí, která se při testování vašeho rozšíření napodobení. Při napodobování zprostředkované služby doporučujeme použít napodobenou architekturu, která implementuje rozhraní vaším jménem a vloží kód, který požadujete konkrétním členům, které váš klient vyvolá. To umožňuje, aby testy pokračovaly v kompilaci a spouštění bez přerušení při přidání členů do zprostředkovaného rozhraní služby.

Při použití sady Microsoft.VisualStudio.Sdk.TestFramework k otestování vašeho rozšíření může test obsahovat standardní kód, který umožňuje navrhnout napodobenou službu, na kterou může klientský kód dotazovat a spouštět. Předpokládejme například, že chcete v testech zprostředkovat zprostředkovanou službu VisualStudioServices.VS2022.FileSystem. Můžete navrhnout napodobení tímto kódem:

IBrokeredServiceContainer sbc = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
Mock<IFileSystem> mockFileSystem = new Mock<IFileSystem>();
sbc.Proffer(VisualStudioServices.VS2022.FileSystem, (ServiceMoniker moniker, ServiceActivationOptions options, IServiceBroker serviceBroker, CancellationToken cancellationToken) => new ValueTask<object?>(mockFileSystem.Object));

Kontejner zprostředkované zprostředkované služby nevyžaduje, aby byla jako samotná sada Visual Studio zaregistrovaná jako první.

Váš kód v rámci testu může zprostředkovanou službu získat jako normální, s tím rozdílem, že v rámci testu získá váš napodobení místo skutečné služby, kterou by se při spuštění v sadě Visual Studio dostal:

IBrokeredServiceContainer sbc = await AsyncServiceProvider.GlobalProvider.GetServiceAsync<SVsBrokeredServiceContainer, IBrokeredServiceContainer>();
IServiceBroker serviceBroker = sbc.GetFullAccessServiceBroker();
IFileSystem? proxy = await serviceBroker.GetProxyAsync<IFileSystem>(VisualStudioServices.VS2022.FileSystem);
using (proxy as IDisposable)
{
    Assumes.Present(proxy);
    await proxy.DeleteAsync(new Uri("file://some/file"), recursive: false, null, this.TimeoutToken);
}