代码之家  ›  专栏  ›  技术社区  ›  Jeff Putz

在WCF调用类上的哪里陷阱失败的连接?

  •  5
  • Jeff Putz  · 技术社区  · 17 年前

    我正在尝试编写一个封装WCF调用的类(如果重要的话,客户机是Silverlight)。这一切都很顺利,但我不知道如何捕获连接失败,就好像服务器不会响应一样。看起来在ChannelFactory生成的代码中的某个地方会发生一些工作,但我不确定。通用代码审查也欢迎。:)

    底线是,围绕通道的创建,或try/catch中的begin或async result委托不会捕获失败的连接。我想让那个catch运行servicecalleror事件。

    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服务器进程。在测试环境中,通过终止客户端系统的网络连接,我得到了同样的结果。

    以下是完整的异常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客户机中使用应用程序级的unhandledException事件。

    7 回复  |  直到 17 年前
        1
  •  8
  •   Jerry Bullard    17 年前

    杰夫,我不完全确定你是如何模拟连接故障的。我假设“好像服务器不响应”意味着您正在寻找某种超时错误。您可能通过在服务器端使用thread.sleep()强制服务调用的慢响应来模拟非响应服务器。正确的?足够接近吗?

    我有一个类似的项目,所以我测试了它,并成功地捕获了超时和其他异常。我认为您可能没有看到这些异常,因为您的自定义绑定有很长的默认超时时间,并且您可能没有等待足够长的时间。

    解释一下,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).
        }
    }
    

    杰瑞

        2
  •  4
  •   Saajid Ismail    17 年前

    我以前也遇到过类似的问题。主要问题在于如何在代理/通道的实例上实现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 ,如果我们使用 前置包装器 内部A类 using 块 Dispose() 即使引发异常,也保证调用方法。中的代码 处置() 方法将处理代理/通道的所有情况和状态。然后可以在此方法中添加错误处理/日志记录/事件委托代码。

    请阅读以下日志以了解更多信息,并了解以上代码的更通用版本: http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service-Proxy-Helper.aspx

        3
  •  0
  •   marc_s MisterSmith    17 年前

    为了捕获与服务器的通信错误,我建议将呼叫 .CreateChannel() 尝试一下……捕捉并处理诸如CommunicationException和TimeoutException之类的东西。

    另外,一般性建议中最重要的一点是:创建channelFactory是一项非常昂贵的操作,因此您可能希望单独执行此操作,并将对channelFactory对象的引用存储在某个地方。

    如果您需要从ChannelFactory创建并可能重新创建通道,那么您可以反复使用ChannelFactory的存储/缓存实例,而无需一直创建该实例。

    但除此之外,我认为你在这里做得很好!

    马克

        4
  •  0
  •   Anderson Imes    17 年前

    当超时发生时,ChannelFactory实例将处于“已中止”状态。您将发现,不是在调用中发生异常时引发异常,而是在调用_channel.dispose()时引发异常。

    你需要以某种方式防范这个例外。最简单的方法是在Dispose方法的内容周围放置一个Try/Catch,但最好在尝试关闭/Dispose之前检查通道实例的状态。

    这就是为什么你仍然得到例外-它不是在这里处理。

    更新:

    根据堆栈跟踪,框架似乎正在尝试执行回调委托,并在那里遇到异常。你在其他地方回答说,试图/捕捉你的代表的内容并不能解决问题…内部异常呢?exception.toString()的完整输出是什么?

    我浏览了.NET框架,它确实可以一直返回到threadpool(至少在stacktrace中是这样),这将导致未处理的异常和线程死亡。如果情况变得更糟,您可以随时接管线程…旋转您自己的线程并在内部同步调用服务,而不是使用内置异步模式(除非这是一种WCF双工通信类型,我认为这不是)。

    我仍然认为完整的异常信息(如果还有其他信息的话)可能有用。

        5
  •  0
  •   John Saunders    17 年前

    杰夫,请给我们看看 你的 尝试在endget调用周围放置try/catch块时的代码。很难相信这没有抓住一个例外,看到它会帮助我相信。

    另外,作为这个实验的一部分,去掉unhandledException事件处理程序。我想只要browserhttprequest意识到有404,你就会发现这个异常。我认为,如果没有unhandledException事件,endget周围的try/catch将捕获异常(如果有)。我认为您正在获取异常处理过程的中间状态。

    我还希望看到您在endget调用后立即放置System.Debugger.Break或其他东西。

        6
  •  0
  •   Christian Hayter    17 年前

    我认为你设计了你的 ServiceCaller 类,以便您可以在 using 街区?

    如果是,那就是问题所在。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 ,您可以根据您在通话结束前成功完成的通话距离提前确定。

    注-只有实际的通道需要这种特殊处理。你可以随意处置工厂。

        7
  •  0
  •   Shiraz Bhaiji    17 年前

    我认为问题在于您设计服务调用程序的方式。

    创建服务调用方时,将打开通道。这意味着通道可以超时,并且您的代码中没有任何可以从超时恢复的内容。

    我会将通道的创建和关闭移动到make call方法。

    您可以围绕begin-get和回调内容执行一个try-catch。