我喜欢在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块再次出现,而不会有隐藏故障状态异常的危险。
那么,使用这些变通方法还有其他需要注意的问题吗?有没有人想出更好的办法?
这是微软推荐的处理WCF客户端调用的方法:
有关更多详细信息,请参见:预期异常
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上问这个问题,以至于微软甚至创建了一个专门的示例来演示如何处理异常:
WCF c: \ WF_WCF_Samples \ \基本\ Client \ ExpectedExceptions \ 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
}
诚然,这是编写的一些普通代码。我目前更喜欢这个答案,并且在代码中没有看到任何可能导致问题的“hack”。
我们的系统架构经常使用Unity IoC框架来创建ClientBase实例,所以没有确定的方法来强制其他开发人员使用{}块。为了使它尽可能的万无一难,我做了这个扩展ClientBase的自定义类,并处理在dispose时关闭通道,或者在finalize时关闭通道,以防有人没有显式地处置Unity创建的实例。
还有一些东西需要在构造函数中完成,为自定义凭证和其他东西设置通道,所以也在这里…
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);
}
}
}
这是什么?
这是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:我把这篇文章做成了一个社区维基。我不会从这个答案中收集“分数”,但如果你同意这个实现,或者编辑它使它更好,我更希望你给它投票。
我的方法是创建一个显式实现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..
}
}
}
注意:这只是dispose的一个简单实现,如果你喜欢,你可以实现更复杂的dispose逻辑。
然后你可以用安全客户端替换常规服务客户端的所有调用,如下所示:
using (MyServiceClientSafe client = new MyServiceClientSafe())
{
var result = client.MyServiceMethod();
}
我喜欢这个解决方案,因为它不需要我访问接口定义,我可以使用using语句,因为我期望,同时允许我的代码看起来或多或少相同。
您仍然需要处理可以抛出的异常,正如在本线程的其他注释中指出的那样。