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
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
이렇게하면 변수를 쉽게 반환 할 수 있습니다.