Aracılığıyla paylaş


Aracılı hizmet kullanma

Bu belgede aracılı herhangi bir hizmetin alınması, genel kullanımı ve elden çıkarılmasıyla ilgili tüm kodlar, desenler ve uyarılar açıklanmaktadır. Satın alındıktan sonra belirli bir aracılı hizmeti kullanmayı öğrenmek için aracılı hizmetin belirli belgelerini arayın.

Bu belgedeki tüm kodlarla, C#'nin null atanabilir başvuru türleri özelliğinin etkinleştirilmesi kesinlikle önerilir.

IServiceBroker alma

Aracılı bir hizmet almak için önce bir örneğine IServiceBrokersahip olmanız gerekir. Kodunuz MEF veya VS Paketi bağlamında çalışırken genellikle genel hizmet aracısını istersiniz.

Aracılı hizmetler, hizmet fabrikaları çağrıldığında kendilerine atananı kullanmalıdırIServiceBroker.

Genel hizmet aracısı

Visual Studio, genel hizmet aracısını almak için iki yol sunar.

komutunuGetServiceAsync kullanarak GlobalProvideröğesini isteyinSVsBrokeredServiceContainer:

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

Visual Studio 2022'den başlayarak, MEF etkinleştirilmiş bir uzantıda çalışan kod genel hizmet aracısını içeri aktarabilir:

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

typeof Import özniteliğinin gerekli olan bağımsız değişkenine dikkat edin.

Genel IServiceBroker için her istek, genel aracılı hizmet kapsayıcısına bir görünüm olarak hizmet veren bir nesnenin yeni bir örneğini oluşturur. Hizmet aracısının bu benzersiz örneği, istemcinizin bu istemcinin kullanımına özgü olayları almasına AvailabilityChanged olanak tanır. Uzantınızdaki her istemcinin/sınıfın bir örnek alıp tüm uzantınızda paylaşmak yerine yukarıdaki yaklaşımlardan birini kullanarak kendi hizmet aracısını almasını öneririz. Bu desen, aracılı bir hizmetin genel hizmet aracısını kullanmaması gereken güvenli kodlama desenlerini de teşvik eder.

Önemli

uygulamaları IServiceBroker genellikle uygulamaz IDisposable, ancak işleyiciler mevcutken AvailabilityChanged bu nesneler toplanamaz. Özellikle kodun işlemin ömrü boyunca hizmet aracısını atabileceği durumlarda olay işleyicilerinin eklenmesini/kaldırılmasını dengelediğinizden emin olun.

Bağlama özgü hizmet aracıları

Uygun hizmet aracısını kullanmak, özellikle Live Share oturumları bağlamında aracılı hizmetlerin güvenlik modelinin önemli bir gereksinimidir.

Aracılı hizmetler kendileriyle IServiceBroker etkinleştirilir ve ile sunulan hizmetler Profferde dahil olmak üzere kendi aracılı hizmet gereksinimleri için bu örneği kullanmalıdır. Bu tür kod, BrokeredServiceFactory örneklenen aracılı hizmet tarafından kullanılacak bir hizmet aracısı alan bir sağlar.

Aracılı hizmet proxy'si alma

Aracılı hizmetin alınması genellikle yöntemiyle GetProxyAsync yapılır.

yöntemi, GetProxyAsync genel tür bağımsız değişkeni olarak bir ServiceRpcDescriptor ve hizmet arabirimi gerektirir. İstediğiniz aracılı hizmetle ilgili belgelerde tanımlayıcının nereden alınacağı ve hangi arabirimin kullanılacağı belirtilmelidir. Visual Studio'ya dahil edilen aracılı hizmetler için, kullanılacak arabirim tanımlayıcıdaki IntelliSense belgelerinde görünmelidir. Kullanılabilir Aracılı Hizmetleri Bulma bölümünde Visual Studio aracılı hizmetleri için tanımlayıcıları bulmayı öğrenin.

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

Tüm aracılı hizmet isteklerinde olduğu gibi yukarıdaki kod da aracılı hizmetin yeni bir örneğini etkinleştirir. Hizmeti kullandıktan sonra, yürütme bloğundan çıktığı için yukarıdaki kod proxy'yi using atacak.

Önemli

Hizmet arabiriminden IDisposabletüretilmese bile alınan her ara sunucu atılmalıdır. Proxy'de genellikle atık toplanmasını engelleyen G/Ç kaynakları olduğundan atma önemlidir. Yok etme, G/Ç'yi sonlandırarak proxy'nin çöp toplamasına izin verir. Yok etmek IDisposable için için için bir koşullu atama kullanın ve gerçekten uygulamayan IDisposableproxy'ler veya proxy'ler için bir özel durumla karşılaşmamak için null atamaya hazır olun.

Bu tür sızıntıları önlemeye yardımcı olmak için en son Microsoft.ServiceHub.Analyzers NuGet paketini yüklediğinizden ve ISBxxxx çözümleyici kurallarını etkin tuttuğunuzdan emin olun.

Proxy'nin atılması, bu istemciye ayrılmış aracılı hizmetin yok edilmesine neden olur.

Kodunuz aracılı bir hizmet gerektiriyorsa ve hizmet kullanılabilir olmadığında çalışmasını tamamlayamıyorsa, özel durum oluşturmak yerine kod kullanıcı deneyimine sahipse kullanıcıya bir hata iletişim kutusu görüntülemek isteyebilirsiniz.

İstemci RPC hedefleri

Bazı aracılı hizmetler "geri çağırmalar" için bir istemci RPC hedefi kabul eder veya gerektirir. Böyle bir seçenek veya gereksinim, söz konusu aracılı hizmetin belgelerinde bulunmalıdır. Visual Studio aracılı hizmetleri için bu bilgiler tanımlayıcıdaki IntelliSense belgelerine eklenmelidir.

Böyle bir durumda, bir istemci aşağıdaki gibi bir istemci ServiceActivationOptions.ClientRpcTarget sağlayabilir:

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

İstemci proxy'sini çağırma

Aracılı hizmet istemenin sonucu, bir ara sunucu tarafından uygulanan hizmet arabiriminin bir örneğidir. Bu ara sunucu, çağrıları ve olayları her yöne iletir ve hizmeti doğrudan çağırırken karşılaşabileceğiniz davranışlardan bazı önemli farklılıklar gösterir.

Gözlemci deseni

Hizmet sözleşmesi türündeki IObserver<T>parametreleri alıyorsa, gözlemci uygulama bölümünden böyle bir tür oluşturma hakkında daha fazla bilgi edinebilirsiniz.

uzantısı ActionBlock<TInput> yöntemiyle AsObserver uygulamak IObserver<T> için uyarlanabilir. Reactive çerçevesinin System.Reactive.Observer sınıfı, arabirimi kendiniz uygulamanın bir diğer alternatifidir.

Proxy'den özel durumlar oluştu

  • Aracılı hizmetten oluşan özel durumlar için atılması beklenebilir RemoteInvocationException . Özgün özel durum içinde InnerExceptionbulunabilir. Bu, uzaktan barındırılan bir hizmet için doğal bir davranıştır çünkü bu, 'den JsonRpcgelen bir davranıştır. Hizmet yerel olduğunda, yerel proxy tüm özel durumları aynı şekilde kaydırarak istemci kodunun yerel ve uzak hizmetler için çalışan tek bir özel durum yoluna sahip olabilmesini sağlar.
    • ErrorCode Hizmet belgelerinde dallayabileceğiniz belirli koşullara göre belirli kodların ayarlandığının belirtilip önerilmediğini denetleyin.
    • daha geniş bir hata kümesi, için temel tür RemoteInvocationExceptionolan yakalanarak RemoteRpcExceptioniletilir.
  • Uzak bir hizmete bağlantı bırakıldığında veya hizmeti barındıran işlem kilitlendiğinde herhangi bir çağrıdan atılması beklenir ConnectionLostException . Bu öncelikle hizmetin uzaktan ne zaman alınabileceğiyle ilgilidir.

Ara sunucu Önbelleğe Alma

Özellikle hizmet uzak bir işlemden geldiğinde aracılı bir hizmetin ve ilişkili proxy'nin etkinleştirilmesinde bazı masraflar vardır. Aracılı hizmetin sık sık kullanılması, ara sunucuyu bir sınıfa yapılan birçok çağrıda önbelleğe almayı garanti ettiğinde, ara sunucu bu sınıftaki bir alanda depolanabilir. İçeren sınıf atılabilir olmalı ve proxy'yi yönteminin içinde Dispose atmalıdır. Bu örneği ele alalım:

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

Yukarıdaki kod kabaca doğrudur, ancak ile SayHiAsyncarasındaki Dispose yarış koşullarını hesaba aktarmaz. Kod ayrıca daha önce alınan proxy'nin atılması ve bir sonraki gerekli olduğunda proxy'nin yeniden talep edilmesine yol açması gereken olayları da hesaba AvailabilityChanged katmıyor.

ServiceBrokerClient sınıfı, kendi kodunuzu basit tutmaya yardımcı olmak için bu yarış ve geçersizleştirme koşullarını işlemek için tasarlanmıştır. Bu yardımcı sınıfı kullanarak ara sunucuyu önbelleğe alan bu güncelleştirilmiş örneği göz önünde bulundurun:

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

Yukarıdaki kod, ve her bir ara sunucu kiralama işleminin ServiceBrokerClient atılmasından sorumludur. Yok etme ve ara sunucu kullanımı arasındaki yarış koşulları nesne tarafından ServiceBrokerClient işlenir; bu nesne, önbelleğe alınan her proxy'yi kendi elden çıkarma sırasında veya bu ara sunucu son kiralandığında (hangisi en son gerçekleşirse) atar.

ile ilgili önemli uyarılar ServiceBrokerClient

IServiceBroker ile ServiceBrokerClient arasında seçim

Her ikisi de kullanıcı dostudur ve varsayılan değer büyük olasılıkla olmalıdır IServiceBroker.

Kategori IServiceBroker ServiceBrokerClient
Kullanıcı dostu Evet Evet
Elden çıkarma gerektirir Hayır Evet
Ara sunucu ömrünü yönetir Hayır. Sahip, proxy'yi kullandığında atmalıdır. Evet, geçerli oldukları sürece canlı tutulur ve yeniden kullanılırlar.
Durum bilgisi olmayan hizmetler için geçerlidir Evet Evet
Durum bilgisi olan hizmetler için geçerlidir Evet Hayır
Olay işleyicileri ara sunucuya eklendiğinde uygun Evet Hayır
Eski ara sunucu geçersiz kılındığında bildirilecek olay AvailabilityChanged Invalidated

ServiceBrokerClient bir ara sunucuyu hızlı ve sık sık yeniden kullanabilmeniz için kullanışlı bir araç sağlar. Burada, temel alınan hizmetin en üst düzey işlemler arasında sizin altınızdan değiştirilmesini önemsemezsiniz.

Ancak bu şeyleri önemsiyorsanız ve proxy'lerinizin ömrünü kendiniz yönetmek istiyorsanız veya olay işleyicilerine ihtiyacınız varsa (bu, proxy'nin ömrünü yönetmeniz gerektiği anlamına gelir), kullanmanız IServiceBrokergerekir.

Hizmet kesintilerine dayanıklılık

Aracılı hizmetlerle mümkün olan birkaç tür hizmet kesintisi vardır:

Aracılı hizmet etkinleştirme hataları

Aracılı bir hizmet isteği kullanılabilir bir hizmet tarafından karşılanabiliyorsa ancak hizmet fabrikası işlenmeyen bir özel durum oluşturduğunda, hatayı anlayıp kullanıcıya bildirebilmeleri için istemciye geri bir ServiceActivationFailedException oluşturulur.

Aracılı bir hizmet isteği kullanılabilir herhangi bir hizmetle eşleştirilemediğinde istemciye null döndürülür. Böyle bir durumda, AvailabilityChanged hizmet daha sonra kullanılabilir olduğunda ve kullanılabilir olduğunda yükseltilecektir.

Hizmet isteği, hizmet orada olmadığından değil, sunulan sürüm istenen sürümden daha düşük olduğundan reddedilebilir. Geri dönüş planınız, istemcinizin var olduğunu bildiği ve etkileşim kurabildiği daha düşük sürümlerle hizmet isteğini yeniden denemeyi içerebilir.

Tüm başarısız sürüm denetimlerinin gecikme süresi fark edilebilir hale gelirse/olduğunda istemci, uzak bir kaynaktan hangi hizmetlerin ve sürümlerin kullanılabilir olduğu hakkında tam bir fikir edinmek için VisualStudioServices.VS2019_4Services.RemoteBrokeredServiceManifest isteğinde bulunabilir.

Bırakılan bağlantıları işleme

Başarıyla alınan aracılı hizmet proxy'si, bağlantının bırakılması veya onu barındıran işlemdeki kilitlenme nedeniyle başarısız olabilir. Böyle bir kesintiden sonra, söz konusu ara sunucuda yapılan tüm çağrılar ConnectionLostException oluşturulur.

Aracılı bir hizmet istemcisi, olayı işleyerek Disconnected bu tür bağlantı düşüşlerini proaktif olarak algılayabilir ve bunlara tepki verebilir. Bu olaya ulaşmak için, nesnesini almak JsonRpc için bir IJsonRpcClientProxy ara sunucu olarak atanmalıdır. Hizmet yerel olduğunda düzgün bir şekilde başarısız olmak için bu atama koşullu olarak yapılmalıdır.

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

Hizmet kullanılabilirliği değişikliklerini işleme

Aracılı hizmet istemcileri, olayı işleyerek daha önce sorguladıkları aracılı bir hizmeti ne zaman yeniden sorgulamaları gerektiğine ilişkin AvailabilityChanged bildirimler alabilir. Bu olaya yönelik işleyiciler, bir hizmet isteği yapıldıktan kısa süre sonra tetiklenen bir olayın yarış durumu nedeniyle kaybolmadığından emin olmak için aracılı bir hizmet istemeden önce eklenmelidir.

Aracılı bir hizmet yalnızca zaman uyumsuz bir yöntemin yürütülmesi süresi boyunca istendiğinde, bu olayın işlenmesi önerilmez. Olay en çok, ara sunucusunu uzun süreler boyunca depolayan istemciler için geçerlidir, böylece hizmet değişikliklerini telafi etmeleri gerekir ve proxy'lerini yenileyebilecek bir konumda olurlar.

Bu olay herhangi bir iş parçacığında, büyük olasılıkla olayın açıklayıcı olduğu bir hizmeti kullanan koda eş zamanlı olarak oluşturulabilir.

Aşağıdakiler dahil olmak üzere çeşitli durum değişiklikleri bu olayın yükseltilmesine neden olabilir:

  • Açılan veya kapatılan bir çözüm veya klasör.
  • Canlı Paylaşım oturumu başlatılıyor.
  • Yeni bulunan dinamik olarak kaydedilmiş aracılı bir hizmet.

Etkilenen aracılı hizmet yalnızca bu olayın, isteğin yerine getirilip getirilmediğine bakılmaksızın daha önce bu hizmeti isteyen istemcilere yükseltilmesine neden olur.

Olay, söz konusu hizmet için yapılan her istek sonrasında hizmet başına en fazla bir kez oluşturulur. Örneğin, istemci A hizmetine istekte bulunursa ve B hizmeti kullanılabilirlik değişikliğiyle karşılaşırsa, bu istemciye hiçbir olay tetiklemez. Daha sonra, A hizmeti bir kullanılabilirlik değişikliğiyle karşılaştığında istemci olayı alır. İstemci A hizmetini yeniden istemezse, A için sonraki kullanılabilirlik değişiklikleri bu istemciye başka bildirim göndermez. İstemci yeniden A isteğinde bulunduktan sonra bu hizmetle ilgili bir sonraki bildirimi almaya uygun hale gelir.

Olay, bir hizmet kullanılabilir olduğunda, artık kullanılamadığında veya önceki tüm hizmet istemcilerinin hizmet için yeniden sorgulamasını gerektiren bir uygulama değişikliğiyle karşılaştığında tetiklenir.

Önbelleğe ServiceBrokerClient alınan proxy'lerle ilgili kullanılabilirlik değişikliği olaylarını, herhangi bir kiralama iade edildiğinde eski proxy'leri yok ederek ve sahibi istekte bulunup bulunmadığında hizmetin yeni bir örneğini isteyerek otomatik olarak işler. Bu sınıf, hizmet durum bilgisi olmadığında ve kodunuzun proxy'ye olay işleyicileri eklemesini gerektirmediğinde kodunuzu önemli ölçüde basitleştirebilir.

Aracılı hizmet kanalı alma

Aracılı hizmete ara sunucu aracılığıyla erişmek en yaygın ve kullanışlı teknik olsa da, gelişmiş senaryolarda istemcinin RPC'yi doğrudan denetleyebilmesi veya diğer veri türlerini doğrudan iletişim kurabilmesi için bu hizmete bir kanal istemek tercih edilebilir veya gerekli olabilir.

Aracılı hizmete bir boru yöntemiyle GetPipeAsync elde edilebilir. Tanımlayıcı tarafından sağlanan RPC davranışları gerekli olmadığından bu yöntem yerine ServiceRpcDescriptor bir alırServiceMoniker. Bir tanımlayıcınız olduğunda, özelliği aracılığıyla ServiceRpcDescriptor.Moniker bu tanımlayıcıdan bir ad edinebilirsiniz.

Borular G/Ç'ye bağlıyken, çöp toplama için uygun değildir. Artık kullanılmayacak olan bu boruları her zaman tamamlayarak bellek sızıntılarından kaçının.

Aşağıdaki kod parçacığında aracılı bir hizmet etkinleştirilir ve istemcinin buna doğrudan bir kanalı vardır. İstemci daha sonra bir dosyanın içeriğini hizmete gönderir ve bağlantıyı keser.

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

Aracılı hizmet istemcilerini test etme

Aracılı hizmetler, uzantınızı test ederken taklit etmek için makul bir bağımlılıktır. Aracılı bir hizmetle alay ederken, arabirimi sizin yerinize uygulayan ve istemcinizin çağıracağı belirli üyelere gereken kodu ekleyen bir sahte çerçeve kullanmanızı öneririz. Bu, üyeleri aracılı hizmet arabirimine eklendiğinde testlerinizin derlemeye ve kesme olmadan çalışmaya devam etmesini sağlar.

Uzantınızı test etmek için Microsoft.VisualStudio.Sdk.TestFramework kullanırken, testiniz istemci kodunuzun sorgulayıp çalıştırabileceği bir sahte hizmet sağlamak için standart kod içerebilir. Örneğin, testlerinizde VisualStudioServices.VS2022.FileSystem aracılı hizmetinin sahtesini yapmak istediğinizi varsayalım. Sahte kodu şu kodla gösterebilirsiniz:

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

Sahte aracılı hizmet kapsayıcısı, Visual Studio'da olduğu gibi önce bir proffered hizmetinin kaydedilmesini gerektirmez.

Test altındaki kodunuz aracılı hizmeti normal şekilde alabilir, ancak test kapsamında Visual Studio altında çalışırken elde edeceği gerçek hizmet yerine sahtenizi alır:

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