题
我正在尝试编写一个封装WCF调用的类(客户端是Silverlight,如果重要的话)。这一切都在游泳,但我不知道如何陷阱连接失败,好像服务器不会响应。似乎在ChannelFactory生成的代码中某处发生了一些工作,但我不确定。也欢迎一般代码审查。 :)
围绕创建通道的底线,或try / catch中的begin或async结果委托不会捕获失败的连接。我想让那个catch运行ServiceCallError事件。
public class ServiceCaller : IDisposable
{
private IFeedService _channel;
public ServiceCaller()
{
var elements = new List<BindingElement>();
elements.Add(new BinaryMessageEncodingBindingElement());
elements.Add(new HttpTransportBindingElement());
var binding = new CustomBinding(elements);
var endpointAddress = new EndpointAddress(App.GetRootUrl() + "Feed.svc");
_channel = new ChannelFactory<IFeedService>(binding, endpointAddress).CreateChannel();
}
public void MakeCall(DateTime lastTime, Dictionary<string, string> context)
{
AsyncCallback asyncCallBack = delegate(IAsyncResult result)
{
var items = ((IFeedService)result.AsyncState).EndGet(result);
if (ItemsRetrieved != null)
ItemsRetrieved(this, new ServiceCallerEventArgs(items));
};
_channel.BeginGet(lastTime, context, asyncCallBack, _channel);
}
public event ItemsRetrievedEventHandler ItemsRetrieved;
public event ServiceCallErrorHandler ServiceCallError;
public delegate void ItemsRetrievedEventHandler(object sender, ServiceCallerEventArgs e);
public delegate void ServiceCallErrorHandler(object sender, ServiceCallErrorEventArgs e);
public void Dispose()
{
_channel.Close();
_channel.Dispose();
}
}
这是堆栈追踪,对于那些好奇的人:
An AsyncCallback threw an exception.
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnGetResponse(IAsyncResult result)
at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClassd.<InvokeGetResponseCallback>b__b(Object state2)
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)
为了实现这一点,我在浏览器中启动应用程序,然后从Visual Studio中删除Web服务器进程。在测试环境中,我通过终止客户端系统的网络连接来获得相同的东西。
这是FULL异常的ToString():
System.Exception: An AsyncCallback threw an exception. ---> System.Exception: An AsyncCallback threw an exception. ---> System.ServiceModel.CommunicationException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound.
at System.Net.Browser.BrowserHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)
at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClass5.<EndGetResponse>b__4(Object sendState)
at System.Net.Browser.AsyncHelper.<>c__DisplayClass2.<BeginOnUI>b__0(Object sendState)
--- End of inner exception stack trace ---
at System.Net.Browser.AsyncHelper.BeginOnUI(SendOrPostCallback beginMethod, Object state)
at System.Net.Browser.BrowserHttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteGetResponse(IAsyncResult result)
--- End of inner exception stack trace ---
at System.ServiceModel.Channels.Remoting.RealProxy.Invoke(Object[] args)
at proxy_2.EndGet(IAsyncResult )
at CoasterBuzz.Feed.Client.ServiceCaller.<MakeCall>b__0(IAsyncResult result)
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
--- End of inner exception stack trace ---
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.CallComplete(Boolean completedSynchronously, Exception exception)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.FinishSend(IAsyncResult result, Boolean completedSynchronously)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.SendCallback(IAsyncResult result)
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
--- End of inner exception stack trace ---
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnGetResponse(IAsyncResult result)
at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClassd.<InvokeGetResponseCallback>b__b(Object state2)
at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)
顺便说一句,我可以捕获这个的唯一方法是在SL客户端中使用app level UnhandledException事件。
解决方案
我有一个类似的项目,所以我测试了它,并能够成功捕获超时和其他异常。我想也许你没有看到这些异常,因为你的自定义绑定有一个很长的默认超时,你可能没有等待足够长的时间。
要解释,WCF超时由绑定配置控制。查看代码,您将创建自己的自定义绑定,如下所示:
var elements = new List<BindingElement>();
elements.Add(new BinaryMessageEncodingBindingElement());
elements.Add(new HttpTransportBindingElement());
var binding = new CustomBinding(elements);
如果在那里设置断点并检查您创建的自定义绑定,您将看到它有一分钟 SendTimeout 默认情况下。我怀疑你要么没有等待足够长的时间,要么没有将服务的模拟超时设置得足够长,所以你无法捕获超时异常。
以下是一些要演示的代码更改。首先,在绑定上设置超时:
var binding = new CustomBinding(elements);
//Set the timeout to something reasonable for your service
//This will fail very quickly
binding.SendTimeout = TimeSpan.FromSeconds(1);
最后,要捕获异常并引发正确的事件,可以按如下方式更新AsyncCallback委托。调用EndGet()时抛出异常。
AsyncCallback asyncCallBack = delegate(IAsyncResult result)
{
IFeedService service = ((IFeedService)result.AsyncState);
try
{
var items = service.EndGet(result);
ItemsRetrieved(this, EventArgs.Empty);
}
catch (System.TimeoutException ex)
{
//Handles timeout
ServiceCallError(this, EventArgs.Empty);
}
catch (System.ServiceModel.CommunicationException ex)
{
//Handles a number of failures:
// Lack of cross-domain policy on target site
// Exception thrown by a service
ServiceCallError(this, EventArgs.Empty);
}
catch (System.Exception ex)
{
//Handles any other errors here
ServiceCallError(this, EventArgs.Empty);
}
};
就一般代码审查而言,我建议您避免对绑定配置进行硬编码。您可以在ServiceReferences.ClientConfig文件中声明端点配置(包括合理的超时),并使用端点名称新建通道工厂,如下所示:
new ChannelFactory<IFeedService>("feedServiceEndpoint");
这应该会使应用程序更易于维护。
我希望这会有所帮助。
杰里
更新:
杰夫,
请查看此代码及其中的注释,以获取有关此处所发生情况的更多说明。 MakeCall()方法正在创建一个传递给BeginGet()方法的函数(委托)。稍后在服务响应时执行该功能。
请进行建议的更改,并在以“AsyncCallback asyncCallback”开头的行处设置断点。和“var items”。您将看到第一次通过调试器只是声明代码(函数/委托)在服务响应时执行,第二次传递是该响应的实际处理,从委托声明的内部开始。这意味着当处理服务调用的响应时,外部try / catch将不在范围内。
public void MakeCall(DateTime lastTime, Dictionary<string, string> context)
{
try
{
AsyncCallback asyncCallBack = delegate(IAsyncResult result)
{
try
{
var items = ((IFeedService)result.AsyncState).EndGet(result);
if (ItemsRetrieved != null)
ItemsRetrieved(this, new ServiceCallerEventArgs(items));
}
catch (Exception ex)
{
//This will catch errors from the service call
}
};
_channel.BeginGet(lastTime, context, asyncCallBack, _channel);
}
catch(Exception ex)
{
//This will not catch an error coming back from the service. It will
//catch only errors in the act of calling the service asynchronously.
//The communication with the service and response is handled on a different
//thread, so this try/catch will be out of scope when that executes.
//So, at this point in the execution, you have declared a delegate
//(actually an anonymous delegate the compiler will turn into a hidden class)
//which describes some code that will be executed when the service responds.
//You can see this in action by setting a breakpoint just inside the delegate
//at the line starting with "var items". It will not be hit until the service
// responds (or doesn't respond in a timeout situation).
}
}
杰里
其他提示
之前我遇到过类似的问题。主要问题是IDisposable在代理/通道实例上的实现方式。我解决它的方式显示在下面的代码中,其中 IDirector
是我的服务合同:
public class ProxyWrapper : IDisposable
{
private IDirector proxy;
private ChannelFactory<IDirector> factory;
int callCount = 0;
public ProxyWrapper()
{
factory = new ChannelFactory<IDirector>();
proxy = factory.CreateChannel();
}
public IDirector Proxy
{
get
{
if (callCount > 0)
throw new InvalidOperationException("The proxy can only be accessed once for every ProxyWrapper instance. You must create a new ProxyWrapper instance for each service request.");
// only allow the proxy/channel to be used for one call.
callCount++;
return proxy;
}
}
public void Dispose()
{
IClientChannel channel = (IClientChannel)proxy;
try
{
if (channel.State != CommunicationState.Faulted)
{
channel.Close();
}
else
{
channel.Abort();
}
}
catch (CommunicationException)
{
channel.Abort();
}
catch (TimeoutException)
{
channel.Abort();
}
catch (Exception)
{
channel.Abort();
throw;
}
finally
{
channel = null;
proxy = null;
}
}
}
使用上述类的方法如下:
public static void Login(string userName, string password)
{
using (ProxyWrapper wrapper = new ProxyWrapper())
{
currentSession = wrapper.Proxy.Login(userName, password);
}
}
因为 ProxyWrapper
类实现 IDisposable
,如果我们在中使用
使用
的 ProxyWrapper
类的实例阻止,即使抛出异常,也可以保证调用 Dispose()
方法。 Dispose()
方法中的代码将处理代理/通道的所有情况和状态。然后,您可以在此方法中添加错误处理/日志记录/事件委托代码。
阅读以下博客文章以获取更多信息,以及上述代码的更通用版本: http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service-Proxy-Helper.aspx
为了捕获与服务器的通信错误,我建议将对 .CreateChannel()
的调用放入try ... catch并处理CommunicationException和TimeoutExceptions等内容。
另外,最常见的一条建议是:创建ChannelFactory是一项非常昂贵的操作,因此您可能需要单独执行此操作并将该引用存储在某处的ChannelFactory对象中。
如果您需要创建并可能从ChannelFactory重新创建频道,则可以反复使用ChannelFactory的存储/缓存实例,而无需一直创建。
但除此之外,我认为你在这里做得很好!
马克
您的ChannelFactory实例将位于“已中止”中发生此超时时的状态。您会发现异常是在调用中发生异常时抛出,而是在调用_channel.Dispose()时抛出异常。
你需要以某种方式防范这种异常。最简单的方法是在dispose方法的内容周围放置一个try / catch,但如果在尝试关闭/处理之前检查_channel实例的状态会更好。
这就是你仍然得到例外的原因 - 这里没有处理。
<强>更新强>
根据您的堆栈跟踪,看起来框架正在尝试执行您的回调委托并在那里获得异常。您在其他地方回答说,尝试/捕获您的委托的内容并不能解决问题......内部异常怎么样? TheException.ToString()的完整输出是什么?
我浏览了.NET Framework,确实允许它一直返回到线程池(至少在你在这里的堆栈跟踪中),这会导致未处理的异常并导致线程死亡。如果情况变得更糟,你可以随时接管线程......启动你自己的线程并在内部同步调用服务,而不是使用内置的异步模式(除非这是一个WCF双工类型的通信,我不知道我认为是。)
我仍然认为完整的异常信息(如果还有其他内容)可能会有用。
Jeff,当您尝试在EndGet调用周围放置try / catch块时,请向我们显示您的代码。很难相信没有例外,看到它会帮助我相信。
此外,作为此实验的一部分,摆脱UnhandledException事件处理程序。我认为,一旦BrowserHttpRequest意识到存在404,我就会捕获此异常。我认为如果没有UnhandledException事件,EndGet周围的try / catch将捕获异常(如果有的话)。我认为你正在接受异常处理过程的中间状态。
我还希望看到你在EndGet调用后立即放置一个System.Debugger.Break或其他东西。
我是否正确地认为您已经设计了 ServiceCaller
类,以便可以使用块在中调用它?
如果是,那就是问题所在。 WCF通道类的设计使得编写完全符合所有可能错误条件的通用处理代码变得非常困难。如果它很简单,那么通道类本身可以安全地处理,你不需要包装类。
我迄今发现的唯一安全方法是封装不是dispose逻辑,而是封装整个调用序列,因此:
public static void Invoke<TContract>(ChannelFactory<TContract> factory, Action<TContract> action) where TContract : class {
var proxy = (IClientChannel) factory.CreateChannel();
bool success = false;
try {
action((TContract) proxy);
proxy.Close();
success = true;
} finally {
if(!success) {
proxy.Abort();
}
}
}
我知道这是同步通话,但原理是一样的。您没有尝试推断是否应该调用 Close
或 Abort
,而是根据您在失败前设置的通话距离来确定它。
注意 - 只有实际频道需要这种特殊处理。您可以按照AFAIK的任何方式处理工厂。
我认为问题在于您设计服务调用者的方式。
创建服务调用者时,将打开该通道。这意味着通道可以超时,代码中没有任何内容可以从时间量中恢复。
我会将通道的创建和关闭移动到make调用方法。
您可以尝试捕获开始获取和回调内容。