[C#] WCF 클라이언트`사용`블록 문제에 대한 가장 좋은 해결 방법은 무엇입니까?

using구현하는 리소스를 사용하는 표준 방법과 거의 비슷하기 때문에 블록 내에서 WCF 서비스 클라이언트를 인스턴스화 하는 것이 좋습니다 IDisposable.

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

그러나이 MSDN 기사 에서 언급 한 것처럼 WCF 클라이언트를 using블록 에 래핑 하면 클라이언트가 오류 상태 (시간 초과 또는 통신 문제 등)로 남아있는 오류를 숨길 수 있습니다. 간단히 말해 Dispose ()가 호출되면 클라이언트의 Close () 메서드가 실행되지만 오류가 발생한 상태에서 오류가 발생합니다. 그런 다음 원래 예외는 두 번째 예외에 의해 가려집니다. 안좋다.

MSDN 기사에서 제안 된 해결 방법은 using블록 사용을 완전히 피하고 대신 클라이언트를 인스턴스화하고 다음과 같이 사용하는 것입니다.

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

using블록과 비교하면 추악하다고 생각합니다. 그리고 클라이언트가 필요할 때마다 쓸 많은 코드가 있습니다.

운 좋게도 IServiceOriented에서 이와 같은 몇 가지 해결 방법을 찾았습니다. 당신은 시작합니다 :

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의 답변 참조 ) 이것이 IDisposable wrapper보다 낫다고 생각 합니다 . 전형적인 코드 :

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

(주석 별 편집)

Use리턴 값이 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이 지적 했듯이이 솔루션에는 상태를 확인할 때 오류가 발생하지 않지만 Close ()가 호출 될 때까지 통신 예외가 발생한다는 경쟁 조건이 포함되어 있습니다.

그래서이 문제를 해결하기 위해 두 세계를 모두 혼합 한 솔루션을 사용했습니다.

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 …)”와 같은 항목을 작성하게됩니다.

나는 모든 것이 고려 된 것이 매우 우아하다는 것을 알았습니다. 발생한 특정 문제가 있습니까?

이를 통해 다른 유용한 기능을 연결할 수 있습니다. 예를 들어 한 사이트에서 사이트는 로그인 한 사용자 대신 서비스를 인증합니다. (사이트 자체에는 자격 증명이 없습니다.) 자체 “UseService”메서드 도우미를 작성하여 원하는 방식으로 채널 팩토리를 구성 할 수 있습니다. 또한 생성 된 프록시를 사용할 수 없습니다. .


답변

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_Samples \ WCF \ Basic \ Client \ ExpectedExceptions \ CS \ client

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

using statement , (heated?) 이 문제에 대한 내부 토론스레드관련된 많은 문제가 있다는 것을 고려할 때 코드 카우보이가되고 더 깨끗한 방법을 찾는 데 시간을 낭비하지 않을 것입니다. 난 그냥 그것을 빨아 내 서버 응용 프로그램에 대한이 자세한 (아직 신뢰할 수있는) 방법으로 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
}

분명히 이것은 쓸만한 평범한 코드입니다. 나는 현재이 답변을 선호 하며, 코드에서 길을 잃을 수도있는 “해킹”을 보지 못한다.


답변

마침내이 문제에 대한 깨끗한 해결책을 향한 확실한 단계를 발견했습니다.

이 사용자 정의 도구는 WCFProxyGenerator를 확장하여 예외 처리 프록시를 제공합니다. ExceptionHandlingProxy<T>상속 되는 추가 프록시를 생성합니다 ExceptionHandlingProxyBase<T>. 후자는 프록시 기능의 고기를 구현합니다. 결과적으로 채널 팩토리 및 채널의 수명 관리 를 상속 ClientBase<T>하거나 ExceptionHandlingProxy<T>캡슐화하는 기본 프록시를 사용하도록 선택할 수 있습니다 . ExceptionHandlingProxy는 비동기 메소드 및 콜렉션 유형과 관련하여 서비스 참조 추가 대화 상자에서 선택한 사항을 존중합니다.

Codeplex 에는 Exception Handling WCF Proxy Generator 프로젝트가 있습니다. 기본적으로 Visual Studio 2008에 새 사용자 지정 도구를 설치 한 다음이 도구를 사용하여 새 서비스 프록시를 생성합니다 (서비스 참조 추가) . 고장난 채널, 타임 아웃 및 안전한 폐기를 처리 할 수있는 훌륭한 기능이 있습니다. 여기 ExceptionHandlingProxyWrapper 라는 훌륭한 비디오가 있는데 이것이 어떻게 작동하는지 정확하게 설명합니다.

Using명령문을 안전하게 다시 사용할 수 있으며 요청에 대해 채널에 결함이있는 경우 (TimeoutException 또는 CommunicationException) 랩퍼는 결함이있는 채널을 다시 초기화하고 쿼리를 재 시도합니다. 실패하면 Abort()명령 을 호출 하고 프록시를 처리하고 예외를 다시 발생시킵니다. 서비스가 FaultException코드를 던지면 실행이 중지되고 프록시가 예상대로 올바른 예외를 안전하게 throw합니다.


답변

Marc Gravell, MichaelGG 및 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));

가능한 “사용”구문에 가깝고 void 메소드를 호출 할 때 더미 값을 반환 할 필요가 없으며 튜플을 사용하지 않고도 서비스를 여러 번 호출하고 여러 값을 반환 할 수 있습니다.

또한 ClientBase<T>원하는 경우 ChannelFactory 대신 하위 항목 과 함께 사용할 수 있습니다 .

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


답변

@ 마크 그 라벨

이것을 사용하는 것은 좋지 않습니까?

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

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