Udostępnij za pośrednictwem


Korzystanie z usługi obsługiwanej przez brokera

W tym dokumencie opisano cały kod, wzorce i ostrzeżenia dotyczące pozyskiwania, ogólnego użytkowania i usuwania dowolnej usługi obsługiwanej przez brokera. Aby dowiedzieć się, jak używać określonej usługi brokera po uzyskaniu, poszukaj konkretnej dokumentacji dla tej usługi obsługiwanej przez brokera.

W przypadku wszystkich kodu w tym dokumencie zdecydowanie zaleca się aktywowanie funkcji typów odwołań dopuszczających wartość null języka C#.

Pobieranie elementu IServiceBroker

Aby uzyskać usługę brokera, musisz najpierw mieć wystąpienie IServiceBrokerklasy . Gdy kod jest uruchamiany w kontekście platformy MEF (Managed Extensibility Framework) lub VSPackage, zwykle potrzebujesz globalnego brokera usług.

Same usługi obsługiwane przez brokera powinny używać przypisanych IServiceBroker do nich po wywołaniu fabryki usług.

Globalny broker usług

Program Visual Studio oferuje dwa sposoby uzyskiwania globalnego brokera usług.

Użyj polecenia GlobalProvider.GetServiceAsync , aby zażądać elementu SVsBrokeredServiceContainer:

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

Począwszy od programu Visual Studio 2022, kod uruchomiony w aktywowanym rozszerzeniu MEF może zaimportować globalnego brokera usług:

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

Zwróć uwagę na typeof argument atrybut Import, który jest wymagany.

Każde żądanie dla globalnego IServiceBroker tworzy nowe wystąpienie obiektu, które służy jako widok do kontenera usługi obsługiwanej przez brokera globalnego. To unikatowe wystąpienie brokera usług umożliwia klientowi odbieranie AvailabilityChanged zdarzeń unikatowych dla tego klienta. Zalecamy, aby każdy klient/klasa w rozszerzeniu nabyć własnego brokera usług przy użyciu jednego z powyższych podejść, zamiast uzyskiwać jedno wystąpienie i udostępniać je w całym rozszerzeniu. Ten wzorzec zachęca również do bezpiecznych wzorców kodowania, w których usługa brokerowana nie powinna używać globalnego brokera usług.

Ważne

Implementacje IServiceBroker programu zwykle nie implementują IDisposableelementu , ale nie można zbierać tych obiektów, gdy AvailabilityChanged istnieją programy obsługi. Pamiętaj, aby zrównoważyć dodawanie/usuwanie procedur obsługi zdarzeń, zwłaszcza gdy kod może odrzucić brokera usług w okresie istnienia procesu.

Brokerzy usług specyficznych dla kontekstu

Korzystanie z odpowiedniego brokera usług jest ważnym wymaganiem modelu zabezpieczeń usług obsługiwanych przez brokera, szczególnie w kontekście sesji live share.

Usługi obsługiwane przez brokera są aktywowane własnymi IServiceBroker usługami i powinny używać tego wystąpienia dla dowolnych potrzeb usługi obsługiwanych przez brokera, w tym usług opisanych za pomocą Profferpolecenia . Taki kod udostępnia element BrokeredServiceFactory , który odbiera brokera usług, który ma być używany przez wystąpienie usługi brokera.

Pobieranie serwera proxy usługi brokera

Pobieranie usługi obsługiwanej przez brokera jest zwykle wykonywane przy użyciu GetProxyAsync metody .

Metoda GetProxyAsync będzie wymagać ServiceRpcDescriptor interfejsu usługi i jako argumentu typu ogólnego. Dokumentacja usługi obsługiwanej przez brokera powinna wskazywać, gdzie uzyskać deskryptor i jakiego interfejsu użyć. W przypadku usług obsługiwanych przez brokera dołączonych do programu Visual Studio interfejs do użycia powinien zostać wyświetlony w dokumentacji funkcji IntelliSense w deskryptorze. Dowiedz się, jak znaleźć deskryptory dla usług obsługiwanych przez brokera programu Visual Studio w artykule Odnajdywanie dostępnych usług obsługiwanych przez brokera.

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();
}

Podobnie jak we wszystkich żądaniach usługi obsługiwanych przez brokera, powyższy kod aktywuje nowe wystąpienie usługi obsługiwanej przez brokera. Po użyciu usługi poprzedni kod usuwa serwer proxy, gdy wykonanie kończy using blok.

Ważne

Każdy pobrany serwer proxy musi zostać usunięty, nawet jeśli interfejs usługi nie pochodzi z IDisposableelementu . Usuwanie jest ważne, ponieważ serwer proxy często zawiera zasoby we/wy, które uniemożliwiają zbieranie pamięci. Usuwanie kończy we/wy, co pozwala serwerowi proxy na odzyskiwanie pamięci. Użyj rzutowania warunkowego do IDisposable dyspozycji i przygotuj się do rzutowania, aby uniknąć wyjątku dla null serwerów proxy lub serwerów proxy, które w rzeczywistości nie implementują IDisposableprogramu .

Pamiętaj, aby zainstalować najnowszy pakiet NuGet Microsoft.ServiceHub.Analyzers i zachować włączone reguły analizatora ISBxxxx, aby zapobiec takim wyciekom.

Usunięcie serwera proxy powoduje usunięcie usługi obsługiwanej przez brokera, która została dedykowana temu klientowi.

Jeśli kod wymaga usługi obsługiwanej przez brokera i nie może ukończyć swojej pracy, gdy usługa jest niedostępna, może zostać wyświetlone okno dialogowe błędu dla użytkownika, jeśli kod jest właścicielem środowiska użytkownika, a nie zgłasza wyjątku.

Cele RPC klienta

Niektóre usługi obsługiwane przez brokera akceptują lub wymagają klienta docelowego wywołania procedury zdalnej (wywołania procedury zdalnej) dla "wywołań zwrotnych". Taka opcja lub wymaganie powinny znajdować się w dokumentacji tej konkretnej usługi obsługiwanej przez brokera. W przypadku usług obsługiwanych przez brokera programu Visual Studio te informacje powinny być zawarte w dokumentacji funkcji IntelliSense w deskryptorze.

W takim przypadku klient może podać go przy użyciu ServiceActivationOptions.ClientRpcTarget następującego polecenia:

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

Wywoływanie serwera proxy klienta

Wynikiem żądania usługi obsługiwanej przez brokera jest wystąpienie interfejsu usługi zaimplementowanego przez serwer proxy. Ten serwer proxy przekazuje wywołania i zdarzenia w każdym kierunku, z pewnymi ważnymi różnicami w zachowaniu tego, czego można oczekiwać podczas bezpośredniego wywoływania usługi.

Wzorzec obserwatora

Jeśli kontrakt usługi przyjmuje parametry typu IObserver<T>, możesz dowiedzieć się więcej o sposobie konstruowania takiego typu w sekcji Jak zaimplementować obserwatora.

Element ActionBlock<TInput> można dostosować do implementacji IObserver<T> za AsObserver pomocą metody rozszerzenia. Klasa System.Reactive.Observer ze struktury Reaktywnej jest kolejną alternatywą dla samodzielnego implementowania interfejsu.

Wyjątki zgłaszane z serwera proxy

  • Spodziewaj się RemoteInvocationException , że zostanie zgłoszony dowolny wyjątek zgłoszony przez usługę brokera. Oryginalny wyjątek można znaleźć w pliku InnerException. Jest to naturalne zachowanie usługi hostowanej zdalnie, ponieważ jest to zachowanie z programu JsonRpc. Gdy usługa jest lokalna, lokalny serwer proxy opakowuje wszystkie wyjątki w taki sam sposób, aby kod klienta mógł mieć tylko jedną ścieżkę wyjątku, która działa dla usług lokalnych i zdalnych.
    • ErrorCode Sprawdź właściwość, jeśli dokumentacja usługi sugeruje, że określone kody są ustawione na podstawie określonych warunków, na których można rozgałęzić.
    • Szerszy zestaw błędów jest przekazywany przez przechwycenie RemoteRpcExceptionelementu , który jest typem podstawowym dla elementu RemoteInvocationException.
  • Spodziewaj się ConnectionLostException , że zostanie zgłoszony z dowolnego wywołania, gdy połączenie z usługą zdalną spadnie lub proces hostowania usługi ulegnie awarii. Jest to przede wszystkim istotne, gdy usługę można uzyskać zdalnie.

Buforowanie serwera proxy

Aktywacja usługi obsługiwanej przez brokera i skojarzonego serwera proxy wiąże się z pewnymi kosztami, szczególnie w przypadku, gdy usługa pochodzi z procesu zdalnego. W przypadku częstego używania usługi obsługiwanej przez brokera buforowanie serwera proxy w wielu wywołaniach do klasy, serwer proxy może być przechowywany w polu w tej klasie. Klasa zawierająca powinna być jednorazowa i usuwana z serwera proxy wewnątrz jego Dispose metody. Rozważ taki przykład:

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();
    }
}

Powyższy kod jest w przybliżeniu poprawny, ale nie uwzględnia warunków wyścigu między Dispose i SayHiAsync. Kod nie uwzględnia AvailabilityChanged również zdarzeń, które powinny prowadzić do wcześniejszego pobrania serwera proxy i ponownego pobrania serwera proxy przy następnym wymaganym czasie.

Klasa jest przeznaczona ServiceBrokerClient do obsługi tych warunków wyścigu i unieważnienia, aby ułatwić zachowanie własnego kodu. Rozważmy ten zaktualizowany przykład, który buforuje serwer proxy przy użyciu tej klasy pomocnika:

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();
    }
}

Poprzedni kod jest nadal odpowiedzialny za usunięcie ServiceBrokerClient i każdego wynajmu serwera proxy. Warunki wyścigu między usuwaniem i używaniem serwera proxy są obsługiwane przez ServiceBrokerClient obiekt, który będzie usuwać każdy buforowany serwer proxy w momencie jego własnej dyspozycji lub kiedy ostatni wynajem tego serwera proxy został wydany, w zależności od tego, co nastąpi ostatnio.

Ważne zastrzeżenia dotyczące ServiceBrokerClient

Wybieranie między elementami IServiceBroker i ServiceBrokerClient

Oba są przyjazne dla użytkownika, a wartością domyślną prawdopodobnie powinna być IServiceBroker.

Kategoria IServiceBroker ServiceBrokerClient
Przyjazny dla użytkownika Tak Tak
Wymaga usunięcia Nie. Tak
Zarządza okresem istnienia serwera proxy L.p. Właściciel musi usunąć serwer proxy po zakończeniu korzystania z niego. Tak, są one przechowywane przy życiu i ponownie używane, o ile są prawidłowe.
Dotyczy usług bezstanowych Tak Tak
Dotyczy usług stanowych Tak Nie.
Odpowiednie, gdy programy obsługi zdarzeń są dodawane do serwera proxy Tak Nie.
Zdarzenie powiadamiania o unieważnieniu starego serwera proxy AvailabilityChanged Invalidated

ServiceBrokerClient zapewnia wygodny sposób szybkiego i częstego ponownego używania serwera proxy, gdzie nie obchodzi cię zmiana podstawowej usługi między operacjami najwyższego poziomu. Jeśli jednak dbasz o te rzeczy i chcesz zarządzać okresem istnienia serwerów proxy lub potrzebujesz programów obsługi zdarzeń (co oznacza, że musisz zarządzać okresem istnienia serwera proxy), należy użyć polecenia IServiceBroker.

Odporność na zakłócenia usług

Istnieje kilka rodzajów zakłóceń usług, które są możliwe w przypadku usług obsługiwanych przez brokera:

Błędy aktywacji usługi obsługiwanej przez brokera

Gdy żądanie obsługi obsługiwanej przez brokera może być spełnione przez dostępną usługę, ale fabryka usług zgłasza nieobsługiwany wyjątek, element ServiceActivationFailedException jest zwracany do klienta, aby mógł zrozumieć i zgłosić błąd użytkownika.

Gdy żądanie usługi obsługiwanej przez brokera nie może być dopasowane do żadnej dostępnej usługi, null jest zwracany do klienta. W takim przypadku zostanie zgłoszony, AvailabilityChanged kiedy i jeśli usługa stanie się dostępna później.

Żądanie obsługi może zostać odrzucone nie dlatego, że usługa nie istnieje, ale ponieważ oferowana wersja jest niższa niż żądana wersja. Plan rezerwowy może obejmować ponawianie próby żądania obsługi z niższymi wersjami, z którymi klient wie, że istnieje i może wchodzić z nimi w interakcje.

Jeśli/gdy opóźnienie ze wszystkich nieudanych kontroli wersji stanie się zauważalne, klient może zażądać VisualStudioServices.VS2019_4Services.RemoteBrokeredServiceManifest, aby uzyskać pełne pojęcie o tym, jakie usługi i wersje są dostępne ze źródła zdalnego.

Obsługa porzuconych połączeń

Pomyślnie pozyskany serwer proxy usługi brokera może zakończyć się niepowodzeniem z powodu porzuconego połączenia lub awarii procesu, który go hostuje. Po takim zakłóceniu każde wywołanie na tym serwerze proxy spowoduje ConnectionLostException zgłoszenie.

Klient usługi brokera może aktywnie wykrywać i reagować na takie spadki połączeń, obsługując Disconnected zdarzenie. Aby osiągnąć to zdarzenie, należy rzutować serwer proxy w JsonRpc celu IJsonRpcClientProxy uzyskania obiektu. Rzutowanie powinno zostać wykonane warunkowo, aby bezpiecznie zakończyć się niepowodzeniem, gdy usługa jest lokalna.

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.
    }
}

Obsługa zmian dostępności usług

Klienci usługi obsługiwanej przez brokera mogą otrzymywać powiadomienia o tym, kiedy powinni ponownie wykonać zapytanie dotyczące usługi obsługiwanej wcześniej przez obsługę AvailabilityChanged zdarzenia. Programy obsługi tego zdarzenia należy dodać przed zażądaniem usługi obsługiwanej przez brokera, aby upewnić się, że zdarzenie zgłoszone wkrótce po zgłoszeniu żądania obsługi nie zostanie utracone z powodu stanu wyścigu.

Gdy usługa brokerowana jest żądana tylko przez czas wykonywania jednej metody asynchronicznej, obsługa tego zdarzenia nie jest zalecana. Zdarzenie jest najbardziej istotne dla klientów, którzy przechowują swój serwer proxy przez dłuższy czas, tak aby musieliby zrekompensować zmiany usługi i są w stanie odświeżyć serwer proxy.

To zdarzenie może być wywoływane w dowolnym wątku, prawdopodobnie współbieżnie do kodu korzystającego z usługi opisywanej przez zdarzenie.

Kilka zmian stanu może prowadzić do podniesienia tego zdarzenia, w tym:

  • Rozwiązanie lub folder jest otwierany lub zamykany.
  • Rozpoczynanie sesji live share.
  • Dynamicznie zarejestrowana usługa brokera, która właśnie została odnaleziona.

Usługa brokera, której dotyczy ten wpływ, powoduje tylko zgłoszenie tego zdarzenia do klientów, którzy wcześniej zażądali tej usługi, niezależnie od tego, czy żądanie zostało spełnione.

Zdarzenie jest wywoływane co najwyżej raz na usługę po każdym żądaniu dla tej usługi. Jeśli na przykład klient żąda usługi A i usługi B wystąpią zmiany dostępności, żadne zdarzenie nie zostanie zgłoszone do tego klienta. Później, gdy usługa A ulegnie zmianie dostępności, klient otrzyma zdarzenie. Jeśli klient nie zażąda ponownie usługi A, kolejne zmiany dostępności dla elementu A nie spowodują dalszych powiadomień dla tego klienta. Gdy klient ponownie zażąda A , otrzyma następne powiadomienie dotyczące tej usługi.

Zdarzenie jest wywoływane, gdy usługa stanie się dostępna, nie jest już dostępna lub występuje zmiana implementacji, która wymaga ponownego zapytania wszystkich wcześniejszych klientów usług dla usługi.

Obsługuje ServiceBrokerClient zdarzenia zmiany dostępności dotyczące buforowanych serwerów proxy automatycznie przez usunięcie starych serwerów proxy, gdy jakiekolwiek wypożyczenia zostały zwrócone i żądanie nowego wystąpienia usługi, gdy i jeśli jego właściciel zażąda jednego. Ta klasa może znacznie uprościć kod, gdy usługa jest bezstanowa i nie wymaga, aby kod dołączał programy obsługi zdarzeń do serwera proxy.

Pobieranie potoku usługi obsługiwanej przez brokera

Chociaż uzyskiwanie dostępu do usługi obsługiwanej przez brokera za pośrednictwem serwera proxy jest najczęstszą i wygodną techniką, w zaawansowanych scenariuszach może być preferowane lub konieczne zażądanie potoku do tej usługi, aby klient mógł kontrolować RPC bezpośrednio lub komunikować się bezpośrednio z dowolnym innym typem danych.

Potok do usługi obsługiwanej przez brokera można uzyskać za pośrednictwem GetPipeAsync metody . Ta metoda przyjmuje wartość ServiceMoniker zamiast, ServiceRpcDescriptor ponieważ zachowania RPC udostępniane przez deskryptor nie są wymagane. Jeśli masz deskryptor, możesz uzyskać z niego pseudonim za pośrednictwem ServiceRpcDescriptor.Moniker właściwości .

Potoki są powiązane z we/wy, ale nie kwalifikują się do odzyskiwania pamięci. Unikaj przecieków pamięci, zawsze wypełniając te potoki, gdy nie będą już używane.

W poniższym fragmencie kodu usługa brokerowana jest aktywowana, a klient ma do niego bezpośredni potok. Następnie klient wysyła zawartość pliku do usługi i rozłącza się.

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();
    }
}

Testowanie klientów usługi obsługiwanej przez brokera

Usługi obsługiwane przez brokera są rozsądną zależnością do pozorowania podczas testowania rozszerzenia. W przypadku wyśmiewanie usługi obsługiwanej przez brokera zalecamy użycie platformy pozorowania, która implementuje interfejs w Twoim imieniu i wprowadza kod wymagany do określonych elementów członkowskich, które będą wywoływane przez klienta. Umożliwia to kontynuowanie kompilowania i uruchamiania testów bez przerw po dodaniu elementów członkowskich do interfejsu usługi obsługiwanej przez brokera.

W przypadku testowania rozszerzenia przy użyciu biblioteki Microsoft.VisualStudio.Sdk.TestFramework może zawierać standardowy kod do wyśmiewanej usługi, dla której kod klienta może wykonywać zapytania i uruchamiać względem nich. Załóżmy na przykład, że chcesz wyśmiewać usługę brokera VisualStudioServices.VS2022.FileSystem w testach. Możesz wywnioskować pozorowanie za pomocą tego kodu:

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));

Wyśmiewany kontener usługi obsługiwanej przez brokera nie wymaga, aby usługa została zarejestrowana jako sama w programie Visual Studio.

Kod testowany może uzyskać usługę brokera w normalny sposób, z tą różnicą, że w ramach testu uzyska pozorny zamiast rzeczywistego, który będzie uruchamiany w programie Visual Studio:

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);
}