programing

WCF 클라이언트의 블록 사용 문제에 대한 최선의 회피책은 무엇입니까?

codeshow 2023. 4. 7. 21:49
반응형

WCF 클라이언트의 블록 사용 문제에 대한 최선의 회피책은 무엇입니까?

를 WCF 에서 하는 것을 .using은 거의에 「블록」을 구현합니다.IDisposable:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

단, 이 MSDN 기사에서 설명한 바와 같이 WCF 클라이언트는usingblock은 클라이언트가 장애 상태에 있는 모든 오류를 마스킹할 수 있습니다(타임아웃 또는 통신 문제 등).한마디로 말해서Dispose()「」가됩니다.Close()메서드는 부팅되지만 장애 상태이기 때문에 오류를 발생시킵니다.그런 다음 두 번째 예외에 의해 원래 예외가 마스킹됩니다.지지않않않않다다

은 MSDN의 입니다.using블록 대신 클라이언트를 인스턴스화하여 다음과 같이 사용합니다.

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

★★★★★★★★★★★★★★★★★★★★와의 비교using블 、 건 、 건 、 건 、 한아 。그리고 고객이 필요할 때마다 많은 코드를 작성해야 합니다.

운 좋게도, 저는 (지금은 없어진) IService Oriented 블로그에서 이와 같은 몇 가지 다른 회피책을 찾았습니다.다음 항목부터 시작합니다.

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 
    
    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

그 결과, 다음과 같은 것이 가능하게 됩니다.

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

않은 이 표현은 할 수 는 아닌 것 .using

현재 사용하려는 회피책은 blog.davidbarret.net에서 처음 읽었습니다.기본적으로 클라이언트의 것을 덮어씁니다.Dispose()사용 장소에 구애받지 마세요.예를 들어 다음과 같습니다.

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

이것에 의해, 다음의 정보가 가능하게 됩니다.using장애 상태의 예외를 마스킹할 위험 없이 다시 차단합니다.

그럼 이 회피책을 사용하기 위해 주의해야 할 다른 방법이 있을까요?누구 더 좋은 생각 없어요?

사실 블로그를 했지만(Luke의 답변 참조), 제 아이디포지터블 포장지보다 나은 것 같아요.표준 코드:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 

(댓글마다 편집)

★★Use void.된 변수 returns void를 입니다. 리턴 값을 처리하는 가장 쉬운 방법은 캡처된 변수를 사용하는 것입니다.

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

IServiceOriented.com이 주창하는 솔루션과 David Barret의 블로그가 주창하는 솔루션 중 하나를 선택할 수 있다면 클라이언트의 Dispose() 메서드를 덮어쓰는 단순함을 선호합니다.이를 통해 using() 문을 사용할 수 있습니다.이것은 처분 가능한 오브젝트에서 예상되는 것과 같습니다.단, @Brian이 지적한 바와 같이 이 솔루션에는 State가 체크되어도 장애가 되지 않지만 Close()가 호출될 때까지 장애가 발생할 수 있는 레이스 조건이 포함되어 있습니다.이 경우 Communication은아직 예외가 발생.

이 문제를 해결하기 위해 저는 두 가지 장점을 혼합한 솔루션을 사용했습니다.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}

제대로 작동하기 위해 고차 함수를 작성했습니다.우리는 이것을 여러 프로젝트에 사용했는데 잘 작동하는 것 같아요.이것은 "사용" 패러다임 없이 처음부터 해야 할 일이다.

TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
{
    var chanFactory = GetCachedFactory<TChannel>();
    TChannel channel = chanFactory.CreateChannel();
    bool error = true;
    try {
        TReturn result = code(channel);
        ((IClientChannel)channel).Close();
        error = false;
        return result;
    }
    finally {
        if (error) {
            ((IClientChannel)channel).Abort();
        }
    }
}

다음과 같이 콜을 발신할 수 있습니다.

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);

이것은 당신의 예시와 거의 비슷합니다.일부 프로젝트에서는 강력한 유형의 도우미 메서드를 작성하기 때문에 "WCF"와 같은 것을 작성하게 됩니다.UseFooService(f=>f...)".

모든 것을 고려해 볼 때 꽤 우아하다고 생각합니다.무슨 문제가 생겼나요?

이것에 의해, 다른 기능을 접속할 수 있습니다.예를 들어 한 사이트에서는 로그인한 사용자를 대신하여 사이트에 서비스가 인증됩니다(사이트 자체에는 자격 증명이 없습니다).독자적인 「Use Service」메서드 헬퍼를 작성하는 것으로, 채널 팩토리를 필요에 따라서 설정할 수 있습니다.생성된 프록시를 사용해야 하는 것도 아닙니다.어떤 인터페이스라도 상관없습니다.

WCF 클라이언트의 콜을 처리하기 위해 Microsoft가 권장하는 방법은 다음과 같습니다.

자세한 내용은 다음을 참조하십시오.예상되는 예외

try
{
    ...
    double result = client.Add(value1, value2);
    ...
    client.Close();
}
catch (TimeoutException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}
catch (CommunicationException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}

추가 정보 WCF에서 많은 사람들이 이 질문을 하고 있는 것 같습니다.Microsoft는 예외 처리 방법을 설명하기 위해 전용 샘플은 다음과 같습니다.

c:\WF_WCF_샘플\WCF\Basic\Client\기대됩니다예외 \CS\클라이언트

샘플 다운로드:C# 또는 VB

사용 설명서와 관련된 문제가 매우 많은 것을 고려하면 (가열) 이 문제에 대한 내부 논의와 실타래, 나는 코드 카우보이가 되어 더 깨끗한 방법을 찾기 위해 시간을 낭비하지 않을 이다.일단 참고 WCF 클라이언트를 서버 애플리케이션에 대해 상세하게(신뢰할 수 있는) 구현합니다.

검출할 수 있는 추가 장애(옵션)

예외가 CommunicationException그리고 그 예외의 대부분은 다시 시도해서는 안 된다고 생각합니다.의 각 하여 재시도 가능한(MSDN과 ).TimeOutException( ) 참참참참주 。재시도해야 할 예외를 놓쳤다면 알려주세요.

  // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
catch (ChannelTerminatedException cte)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
// reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
catch (EndpointNotFoundException enfe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following exception that is thrown when a server is too busy to accept a message.
catch (ServerTooBusyException stbe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

인정하건대, 이것은 쓰기에는 다소 평범한 코드이다.저는 현재 이 답변을 선호하고 있으며, 향후 문제를 일으킬 수 있는 코드에는 "핵"이 없습니다.

나는 마침내 이 문제에 대한 깨끗한 해결을 위한 몇 가지 확실한 조치를 찾았다.

WCFProxy Generator는 WCFProxy Generator를 사용합니다.가 추가로 됩니다.ExceptionHandlingProxy<T>ExceptionHandlingProxyBase<T>을 구현합니다 - 프록시 기능을 합니다., 「」를 계승하는 할 수 .ClientBase<T> ★★★★★★★★★★★★★★★★★」ExceptionHandlingProxy<T>채널 팩토리 및 채널의 라이프 타임 관리를 캡슐화합니다.Exception Handling Proxy (예외 처리 프록시(Add Service Reference)화상자 선택을 존중합니다.

Codeplex에는 예외 처리 WCF 프록시 생성기라는 프로젝트가 있습니다.기본적으로 Visual Studio 2008에 새로운 커스텀툴을 인스톨 하고 나서, 이 툴을 사용해 새로운 서비스 프록시(서비스 레퍼런스 추가)를 생성합니다.고장난 채널, 시간 초과 및 안전한 폐기에 대처하기 위한 몇 가지 기능이 있습니다.여기 Exception Handling Proxy Wrapper라는 훌륭한 비디오가 있습니다.이 비디오의 원리에 대해 설명합니다.

하실 수 .Using했을 경우에 대해 다시 스테이트먼저 Timeout (Timeout) Communication(예외을 다시 합니다.예외) Wrapper는 장애가 발생한 채널을 다시 초기화하고 쿼리를 재시도합니다.는, 「」, 「」를 호출합니다.Abort()명령을 실행하여 프록시를 삭제하고 예외를 다시 던집니다.가 음음음 a a a a a a a a a를 FaultException실행이 중지되고 프록시가 안전하게 중단되어 예상대로 올바른 예외가 발생합니다.

Marc Gravel, Michael GG 및 Matt Davis의 답변을 바탕으로 개발자는 다음과 같은 사항을 제시했습니다.

public static class UsingServiceClient
{
    public static void Do<TClient>(TClient client, Action<TClient> execute)
        where TClient : class, ICommunicationObject
    {
        try
        {
            execute(client);
        }
        finally
        {
            client.DisposeSafely();
        }
    }

    public static void DisposeSafely(this ICommunicationObject client)
    {
        if (client == null)
        {
            return;
        }

        bool success = false;

        try
        {
            if (client.State != CommunicationState.Faulted)
            {
                client.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                client.Abort();
            }
        }
    }
}

사용 예:

string result = string.Empty;

UsingServiceClient.Do(
    new MyServiceClient(),
    client =>
    result = client.GetServiceResult(parameters));

가능한 한 "using" 구문에 가깝기 때문에 void 메서드를 호출할 때 더미 값을 반환할 필요가 없습니다.또한 tuples를 사용하지 않고도 서비스에 여러 콜을 발신할 수 있습니다(및 여러 값을 반환할 수 있습니다).

, 능, 능, 이, 이, 이, also, also, also, also, also과 같이ClientBase<T>Channel Factory 。

확장 방식은 개발자가 프록시/채널을 수동으로 폐기하려는 경우 노출됩니다.

@마크 그라벨

이것을 사용해도 좋지 않을까요?

public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
        where T : ICommunicationObject
{
    try
    {
        var result = work(client);

        client.Close();

        return result;
    }
    catch (Exception e)
    {
        client.Abort();

        throw;
    }
}

같은 것, 같은 것, 같은 것(Func<T, TResult>)의 경우Service<IOrderService>.Use

이렇게 하면 변수를 더 쉽게 반환할 수 있습니다.

이게 뭐야?

이는 승인된 답변의 CW 버전이지만 (완전하다고 생각되는) 예외 처리가 포함되어 있습니다.

승인된 답변은 더 이상 존재하지 않는 이 웹 사이트를 참조합니다.수고를 덜기 위해 가장 적절한 부분을 여기에 포함시켰습니다.또한 이러한 귀찮은 네트워크 타임아웃을 처리하기 위해 예외 리트라이 처리를 포함하도록 약간 수정했습니다.

간단한 WCF 클라이언트 사용방법

클라이언트측 프록시를 생성하면 이것만 구현하면 됩니다.

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
});

ServiceDelegate.cs

솔루션에 이 파일을 추가합니다.재시도 횟수나 처리할 예외를 변경하지 않는 한 이 파일을 변경할 필요가 없습니다.

public delegate void UseServiceDelegate<T>(T proxy);

public static class Service<T>
{
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock)
    {
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
        bool success = false;


       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }

           // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
           catch (ChannelTerminatedException cte)
           {
              mostRecentEx = cte;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep  * (i + 1)); 
           }

           // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
           // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
           catch (EndpointNotFoundException enfe)
           {
              mostRecentEx = enfe;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // The following exception that is thrown when a server is too busy to accept a message.
           catch (ServerTooBusyException stbe)
           {
              mostRecentEx = stbe;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch (TimeoutException timeoutEx)
           {
               mostRecentEx = timeoutEx;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           } 
           catch (CommunicationException comException)
           {
               mostRecentEx = comException;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch(Exception )
           {
                // rethrow any other exception not defined here
                // You may want to define a custom Exception class to pass information such as failure count, and failure type
                proxy.Abort();
                throw ;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

PS: 이 글을 커뮤니티 Wiki로 만들었습니다.이 답변에서 "포인트"를 수집하지는 않지만, 구현에 동의하는 경우 업그레이드하거나 수정하여 개선하십시오.

다음은 질문에서 확장된 소스 버전으로 여러 채널팩토리를 캐시하고 컨피규레이션파일 내의 엔드포인트를 계약명으로 검색하도록 확장한 것입니다.

를 사용합니다. 4 NET 4(LINQ, LINQ),var

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}

다음과 같은 포장지가 작동합니다.

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

그러면 다음과 같은 코드를 작성할 수 있습니다.

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

물론 필요한 경우 래퍼는 더 많은 예외를 포착할 수 있지만 원칙은 동일합니다.

Castle dynamic proxy를 사용하여 Dispose() 문제를 해결하고 사용할 수 없는 상태일 때 채널을 자동으로 새로 고칩니다.이를 사용하려면 서비스 계약 및 IDisposable을 상속하는 새 인터페이스를 생성해야 합니다.다이내믹 프록시는 다음 인터페이스를 구현하고 WCF 채널을 랩합니다.

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

소비자가 WCF에 대해 걱정할 필요 없이 WCF 서비스를 주입할 수 있어 좋습니다.또한 다른 솔루션과 같은 까다로운 기능은 없습니다.

코드를 봐주세요.사실 매우 간단합니다.WCF Dynamic Proxy

확장 방식 사용:

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}

IoC가 필요하지 않거나 자동 생성된 클라이언트(서비스 참조)를 사용하는 경우 래퍼를 사용하여 마감을 관리하고 GC가 예외를 발생시키지 않는 안전한 상태에 있을 때 클라이언트 베이스를 사용할 수 있습니다.GC가 서비스 클라이언트의 Dispose를 호출하고, 이것이 호출합니다.Close·모두 닫혀 있기 때문에, 파손은 없습니다.생산코드상 문제없이 사용하고 있습니다.

public class AutoCloseWcf : IDisposable
{

    private ICommunicationObject CommunicationObject;

    public AutoDisconnect(ICommunicationObject CommunicationObject)
    {
        this.CommunicationObject = CommunicationObject;
    }

    public void Dispose()
    {
        if (CommunicationObject == null)
            return;
        try {
            if (CommunicationObject.State != CommunicationState.Faulted) {
                CommunicationObject.Close();
            } else {
                CommunicationObject.Abort();
            }
        } catch (CommunicationException ce) {
            CommunicationObject.Abort();
        } catch (TimeoutException toe) {
            CommunicationObject.Abort();
        } catch (Exception e) {
            CommunicationObject.Abort();
            //Perhaps log this

        } finally {
            CommunicationObject = null;
        }
    }
}

서버에 할 때 하여 " " " " " " 를 사용합니다.using동동: :

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}

요약

이 답변에서 설명하는 기술을 사용하면 다음 구문을 사용하여 사용 중인 블록에서 WCF 서비스를 소비할 수 있습니다.

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

더 프로그래밍 모델을 수 , 은 '하다', '하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', '실행하다', 'IMyService일회용 패턴을 올바르게 구현하는 채널을 재지정합니다.


세부 사항

하고 있습니다.IDisposable 간결한 모델을 하는 것 」를 할 수 .using관리되지 않는 리소스에 대해 폐기하는 블록)이 이 블록입니다.이 블록에서는 프록시가 변경되어 구현됩니다.IDisposable버그 없는 실장.이 접근방식의 문제는 유지보수성입니다.사용하는 프록시에 대해서, 이 기능을 재실장할 필요가 있습니다.이 답변의 변형에서는 이 기술을 일반화하기 위해 상속이 아닌 구성을 사용하는 방법을 볼 수 있습니다.

첫 번째 시도

에는 다양한 구현이 있는 것 같습니다.IDisposable구현, 그러나 논의를 위해 현재 승인된 답변에 사용되는 적응을 사용할 것입니다.

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void DoWork();
}

public class ProxyDisposer : IDisposable
{
    private IClientChannel _clientChannel;


    public ProxyDisposer(IClientChannel clientChannel)
    {
        _clientChannel = clientChannel;
    }

    public void Dispose()
    {
        var success = false;
        try
        {
            _clientChannel.Close();
            success = true;
        }
        finally
        {
            if (!success)
                _clientChannel.Abort();
            _clientChannel = null;
        }
    }
}

public class ProxyWrapper : IMyService, IDisposable
{
    private IMyService _proxy;
    private IDisposable _proxyDisposer;

    public ProxyWrapper(IMyService proxy, IDisposable disposable)
    {
        _proxy = proxy;
        _proxyDisposer = disposable;
    }

    public void DoWork()
    {
        _proxy.DoWork();
    }

    public void Dispose()
    {
        _proxyDisposer.Dispose();
    }
}

위의 클래스로 무장하고 이제 글을 쓸 수 있습니다.

public class ServiceHelper
{
    private readonly ChannelFactory<IMyService> _channelFactory;

    public ServiceHelper(ChannelFactory<IMyService> channelFactory )
    {
        _channelFactory = channelFactory;
    }

    public IMyService CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return new ProxyWrapper(channel, channelDisposer);
    }
}

을 사용하면 할 수 .using 삭제:

ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

범용화

지금까지 우리가 한 일은 토마스의 해결책을 재구성한 것뿐이다.이 코드가 일반적이지 않은 것은ProxyWrapper원하는 모든 서비스 계약에 대해 재평가해야 합니다. IL IL을 으로 이 수 .

public class ServiceHelper<T>
{
    private readonly ChannelFactory<T> _channelFactory;

    private static readonly Func<T, IDisposable, T> _channelCreator;

    static ServiceHelper()
    {
        /** 
         * Create a method that can be used generate the channel. 
         * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
         * */
        var assemblyName = Guid.NewGuid().ToString();
        var an = new AssemblyName(assemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);

        var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));

        var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
            new[] { typeof(T), typeof(IDisposable) });

        var ilGen = channelCreatorMethod.GetILGenerator();
        var proxyVariable = ilGen.DeclareLocal(typeof(T));
        var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
        ilGen.Emit(OpCodes.Ldarg, proxyVariable);
        ilGen.Emit(OpCodes.Ldarg, disposableVariable);
        ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
        ilGen.Emit(OpCodes.Ret);

        _channelCreator =
            (Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));

    }

    public ServiceHelper(ChannelFactory<T> channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public T CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return _channelCreator(channel, channelDisposer);
    }

   /**
    * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
    * This method is actually more generic than this exact scenario.
    * */
    private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
    {
        TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            TypeAttributes.Public | TypeAttributes.Class);

        var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
            tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));

        #region Constructor

        var constructorBuilder = tb.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            interfacesToInjectAndImplement);

        var il = constructorBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i);
            il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
        }
        il.Emit(OpCodes.Ret);

        #endregion

        #region Add Interface Implementations

        foreach (var type in interfacesToInjectAndImplement)
        {
            tb.AddInterfaceImplementation(type);
        }

        #endregion

        #region Implement Interfaces

        foreach (var type in interfacesToInjectAndImplement)
        {
            foreach (var method in type.GetMethods())
            {
                var methodBuilder = tb.DefineMethod(method.Name,
                    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                    MethodAttributes.Final | MethodAttributes.NewSlot,
                    method.ReturnType,
                    method.GetParameters().Select(p => p.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();

                if (method.ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);
                    il.Emit(OpCodes.Callvirt, method);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    il.DeclareLocal(method.ReturnType);

                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);

                    var methodParameterInfos = method.GetParameters();
                    for (var i = 0; i < methodParameterInfos.Length; i++)
                        il.Emit(OpCodes.Ldarg, (i + 1));
                    il.Emit(OpCodes.Callvirt, method);

                    il.Emit(OpCodes.Stloc_0);
                    var defineLabel = il.DefineLabel();
                    il.Emit(OpCodes.Br_S, defineLabel);
                    il.MarkLabel(defineLabel);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                }

                tb.DefineMethodOverride(methodBuilder, method);
            }
        }

        #endregion

        return tb.CreateType();
    }
}

새로운 도우미 클래스로 이제 글을 쓸 수 있습니다.

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

, 「」의 를 상속하는 경우에도, 같은 수법)을할 수 해 주세요.ClientBase<>)ChannelFactory<>IDisposable널을닫닫 닫닫닫다다

접속을 종료하는 방법은 다음과 같습니다.

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}

이 문제를 다루는 간단한 기본 클래스를 작성했습니다.NuGet 패키지로 사용할 수 있어 사용하기 편리합니다.

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

따라서 반환문을 적절하게 작성할 수 있습니다.

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 

ChannelFactory가 아닌 ServiceClient를 사용하는 경우 Marc Gravell의 답변에서 서비스 구현을 추가하고 싶습니다.

public interface IServiceConnector<out TServiceInterface>
{
    void Connect(Action<TServiceInterface> clientUsage);
    TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
}

internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
    where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
{
    public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
    {
        var result = default(TResult);
        Connect(channel =>
        {
            result = channelUsage(channel);
        });
        return result;
    }

    public void Connect(Action<TServiceInterface> clientUsage)
    {
        if (clientUsage == null)
        {
            throw new ArgumentNullException("clientUsage");
        }
        var isChanneldClosed = false;
        var client = new TService();
        try
        {
            clientUsage(client);
            client.Close();
            isChanneldClosed = true;
        }
        finally
        {
            if (!isChanneldClosed)
            {
                client.Abort();
            }
        }
    }
}

관심 있는 분들을 위해 VB가 있습니다.승인된 답변의 NET 번역(아래).이 스레드에 다른 사람의 힌트를 조합하여 간결하게 하기 위해 조금 다듬었습니다.

발신 태그(C#)에 대해서는 주제에서 벗어난 것은 인정하지만, VB를 찾을 수 없었기 때문에.이 훌륭한 솔루션의 NET 버전도 다른 솔루션에서 찾을 수 있을 것이라고 생각합니다.람다 번역은 조금 까다로울 수 있기 때문에 누군가 수고를 덜고 싶습니다.

에서는, 「 」, 「 」를 할 수 .ServiceEndpoint행행시 。


코드:

Namespace Service
  Public NotInheritable Class Disposable(Of T)
    Public Shared ChannelFactory As New ChannelFactory(Of T)(Service)

    Public Shared Sub Use(Execute As Action(Of T))
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Sub



    Public Shared Function Use(Of TResult)(Execute As Func(Of T, TResult)) As TResult
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Use = Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Function



    Public Shared ReadOnly Property Service As ServiceEndpoint
      Get
        Return New ServiceEndpoint(
          ContractDescription.GetContract(
            GetType(T),
            GetType(Action(Of T))),
          New BasicHttpBinding,
          New EndpointAddress(Utils.WcfUri.ToString))
      End Get
    End Property
  End Class
End Namespace

사용방법:

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
  End Get
End Property

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
  End Get
End Property

델의 시스템 아키텍처에서는 UnityIoC 프레임워크를 사용하여 ClientBase 인스턴스를 작성하는 경우가 많기 때문에 다른 개발자가 이를 사용하도록 강제할 수 있는 방법은 없습니다.using{}커스텀클래스는 작성한 인스턴스를확장하고 .가능한 한 확실하게 하기 위해 ClientBase를 확장하고 폐기 시 또는 Unity가 작성한 인스턴스를 명시적으로 폐기하지 않을 경우 채널 폐쇄를 처리하는 커스텀클래스를 만들었습니다.

커스텀 credential 등의 채널을 셋업하기 위해 컨스트럭터에서 수행해야 할 작업도 있습니다.그것도 여기에 있습니다.

public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
    private bool disposed = false;

    public PFServer2ServerClientBase()
    {
        // Copy information from custom identity into credentials, and other channel setup...
    }

    ~PFServer2ServerClientBase()
    {
        this.Dispose(false);
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            try
            {
                    if (this.State == CommunicationState.Opened)
                        this.Close();
            }
            finally
            {
                if (this.State == CommunicationState.Faulted)
                    this.Abort();
            }
            this.disposed = true;
        }
    }
}

그 후, 클라이언트는 다음과 같이 간단하게 할 수 있습니다.

internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
    public string TestMethod(int value)
    {
        return base.Channel.TestMethod(value);
    }
}

발신자는, 다음의 어느쪽인가를 실행할 수 있습니다.

public SomeClass
{
    [Dependency]
    public ITest test { get; set; }

    // Not the best, but should still work due to finalizer.
    public string Method1(int value)
    {
        return this.test.TestMethod(value);
    }

    // The good way to do it
    public string Method2(int value)
    {
        using(ITest t = unityContainer.Resolve<ITest>())
        {
            return t.TestMethod(value);
        }
    }
}

이 투고에 대한 답변은 거의 없고, 필요에 따라 커스터마이즈했습니다.

에 WCF 클라이언트를 사용하여 를 할 수을 원했기 에 WCF는 WCF 클라이언트를 사용합니다.DoSomethingWithClient()★★★★★★ 。

public interface IServiceClientFactory<T>
{
    T DoSomethingWithClient();
}
public partial class ServiceClient : IServiceClientFactory<ServiceClient>
{
    public ServiceClient DoSomethingWithClient()
    {
        var client = this;
        // do somthing here as set client credentials, etc.
        //client.ClientCredentials = ... ;
        return client;
    }
}

도우미 클래스는 다음과 같습니다.

public static class Service<TClient>
    where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
{
    public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
    {
        TClient client = default(TClient);
        bool success = false;
        try
        {
            client = new TClient().DoSomethingWithClient();
            TReturn result = codeBlock(client);
            client.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }
}

다음과 같이 사용할 수 있습니다.

string data = Service<ServiceClient>.Use(x => x.GetData(7));

다음과 같이 Dispose를 구현하는 채널의 자체 래퍼가 있습니다.

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

이것은 잘 작동하는 것처럼 보이며 사용 블록을 사용할 수 있습니다.

는 음을 호출할 수 있습니다.voidnon-time-internal-internal-internal.

var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
var sum = calculator.Invoke(c => c.Sum(42, 42));
calculator.Invoke(c => c.RebootComputer());

클래스 자체는 다음과 같습니다.

public class WcfInvoker<TService>
    where TService : ICommunicationObject
{
    readonly Func<TService> _clientFactory;

    public WcfInvoker(Func<TService> clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public T Invoke<T>(Func<TService, T> action)
    {
        var client = _clientFactory();
        try
        {
            var result = action(client);
            client.Close();
            return result;
        }
        catch
        {
            client.Abort();
            throw;
        }
    }

    public void Invoke(Action<TService> action)
    {
        Invoke<object>(client =>
        {
            action(client);
            return null;
        });
    }
}

ClientBase를 기반으로 프록시 클래스를 생성할 필요 없이 클라이언트의 Dispose()를 덮어씁니다(WcfClient는 AXTract 클래스가 아니며 ClientBase를 기반으로 합니다).

// No need for a generated proxy class
//using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
//{
//    results = orderService.GetProxy().PlaceOrder(input);
//}

public class WcfClient<TService> : ClientBase<TService>, IDisposable
    where TService : class
{
    public WcfClient()
    {
    }

    public WcfClient(string endpointConfigurationName) :
        base(endpointConfigurationName)
    {
    }

    public WcfClient(string endpointConfigurationName, string remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    protected virtual void OnDispose()
    {
        bool success = false;

        if ((base.Channel as IClientChannel) != null)
        {
            try
            {
                if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                {
                    (base.Channel as IClientChannel).Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    (base.Channel as IClientChannel).Abort();
                }
            }
        }
    }

    public TService GetProxy()
    {
        return this.Channel as TService;
    }

    public void Dispose()
    {
        OnDispose();
    }
}

이 작업을 수행하는 방법은 IDisposable을 명시적으로 구현하는 상속 클래스를 만드는 것입니다.이 기능은 GUI를 사용하여 서비스 레퍼런스(서비스 레퍼런스 추가)를 추가하는 사용자에게 유용합니다.서비스 참조를 만드는 프로젝트에서 이 클래스를 삭제하고 기본 클라이언트 대신 사용합니다.

using System;
using System.ServiceModel;
using MyApp.MyService; // The name you gave the service namespace

namespace MyApp.Helpers.Services
{
    public class MyServiceClientSafe : MyServiceClient, IDisposable
    {
        void IDisposable.Dispose()
        {
            if (State == CommunicationState.Faulted)
            {
                Abort();
            }
            else if (State != CommunicationState.Closed)
            {
                Close();
            }

            // Further error checks and disposal logic as desired..
        }
    }
}

주의: 이것은 단순한 폐기 구현입니다.필요에 따라 더 복잡한 폐기 로직을 구현할 수 있습니다.

그런 다음 일반 서비스 클라이언트에서 발신된 모든 콜을 다음과 같이 안전한 클라이언트로 대체할 수 있습니다.

using (MyServiceClientSafe client = new MyServiceClientSafe())
{
    var result = client.MyServiceMethod();
}

" "를 할 수 때문에 .using내 코드가 거의 동일하게 보이도록 하면서도 예상대로 문장을 작성합니다.

이 스레드의 다른 코멘트에서 지적된 대로 발생할 수 있는 예외를 처리해야 합니다.

,도할 수 .DynamicProxyDispose()과 같은을 할 수 .이렇게 하면 다음과 같은 작업을 수행할 수 있습니다.

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}

언급URL : https://stackoverflow.com/questions/573872/what-is-the-best-workaround-for-the-wcf-client-using-block-issue

반응형