【问题标题】:Best way to catch a WCF exception in Silverlight?在 Silverlight 中捕获 WCF 异常的最佳方法是什么?
【发布时间】:2010-09-10 19:28:39
【问题描述】:

我有一个使用 WCF 服务的 Silverlight 2 应用程序。因此,它对服务方法的所有调用使用异步回调。如果在这些调用之一之前或期间服务未运行,或者它崩溃,或者网络出现故障等,则会如您预期的那样生成异常。问题是,我不知道如何捕捉这个异常。

  • 因为它是一个异步调用,所以我不能用 try/catch 块包装我的 begin 调用,并让它拾取在程序从该点继续运行后发生的异常。

  • 因为服务代理是自动生成的,所以我不能在每个生成的调用 EndInvoke 的函数上放置一个 try/catch 块(异常实际出现的地方)。这些生成的函数也被调用堆栈中的外部代码包围,因此堆栈中也没有其他地方可以放置 try/catch。

  • 我无法将 try/catch 放入回调函数中,因为异常在它们被调用之前就发生了。

  • 我的 App.xaml.cs 中有一个 Application_UnhandledException 函数,它捕获所有未处理的异常。我可以使用它,但这似乎是一种混乱的方式。我宁愿将此函数保留给真正意外的错误(也称为错误),而不是在我想以特定方式处理的每种情况下都使用此函数中的代码。

我是否缺少明显的解决方案?还是我使用 Application_UnhandledException 卡住了?

[编辑]
如下所述,Error 属性正是我想要的。让我陷入循环的是,异常被抛出并且似乎未被捕获,但执行能够继续。它触发 Application_UnhandledException 事件并导致 VS2008 中断执行,但在调试器中继续执行允许继续执行。这不是真正的问题,只是看起来很奇怪。

【问题讨论】:

    标签: wcf silverlight exception


    【解决方案1】:

    要处理这种情况,请使用 TargetInvocationException。当网络关闭或您的服务不可用时,这将捕获异常。

    【讨论】:

      【解决方案2】:

      在 WP7 上使用 XNA,我发现我别无选择,只能手动将 try/catch 添加到各种异步 End*FunctionName*() 方法;当服务器不可用时,我尝试过的任何其他方法都无法防止应用程序失败和关闭。当服务发生变化时,必须手动更新此代码是一个真正的麻烦。

      我很惊讶这不是一个更大的问题,因为似乎没有任何其他方法可以使用 XNA 在 WP7 上捕获这些异常,但我想这只是更多地说明了有多少(==不是很多)人试图做到这一点比什么都重要。如果他们只是让 slsvcutil.exe 生成同步方法,我们可以轻松地在我们自己的工作线程中捕获这些方法,但不幸的是,生成的代码使用线程池线程,无法捕获异常。

      【讨论】:

        【解决方案3】:

        使用自定义 WCF 代理生成器是在 Silver-light 中处理异步异常的一个很好的解决方案。单击here 下载源代码。

        【讨论】:

          【解决方案4】:

          我在服务方法完成事件处理程序中检查事件参数的错误属性。我没有遇到未调用事件处理程序的问题。在服务器出现故障的情况下,调用需要几秒钟,然后在 Error 属性中返回 ProtocolException。

          假设您已经尝试过,并且您的回调实际上从未被调用,您可能会考虑自定义生成的代理类。见this article

          【讨论】:

          • 澄清一下,“服务方法回调”是指方法完成事件,不是回调。回调与 APM 一起使用,而 Silverlight 使用 EAP。
          【解决方案5】:

          我不是管道工,所以我决定创建自己的 WCF 服务类,它覆盖了 Visual Studio 自动生成的类文件“reference.cs”的一些功能,然后我添加了自己的 try/捕获块以捕获通信错误。

          我创建的类看起来像这样:

          public class myWCFService : MyWCFServiceClient
          {
          
              protected override MyController.MyService.IMyWCFService CreateChannel()
              {
                  return new MyWCFServiceClientChannel(this);
              }
          
          }
          
          private class MyWCFServiceClientChannel : ChannelBase<MyController.MyService.IMyWCFService>, MyController.MyService.IMyWCFService
          {
              /// <summary>
              /// Channel Constructor
              /// </summary>
              /// <param name="client"></param>
              public MyWCFServiceClientChannel(System.ServiceModel.ClientBase<MyController.MyService.IMyWCFService> client) :
              base(client)
              {
              }
              /// <summary>
              /// Begin Call To RegisterUser
              /// </summary>
              /// <param name="memberInformation"></param>
              /// <param name="callback"></param>
              /// <param name="asyncState"></param>
              /// <returns></returns>
              public System.IAsyncResult BeginRegisterUser(MyDataEntities.MembershipInformation memberInformation, System.AsyncCallback callback, object asyncState)
              {               
                  object[] _args = new object[1];
                  _args[0] = memberInformation;
                  System.IAsyncResult _result = base.BeginInvoke("RegisterUser", _args, callback, asyncState);
                  return _result;               
              }
              /// <summary>
              /// Result from RegisterUser
              /// </summary>
              /// <param name="result"></param>
              /// <returns></returns>
              public MyDataEntities.MembershipInformation EndRegisterUser(System.IAsyncResult result)
              {
                  try
                  {
                      object[] _args = new object[0];
                      MyDataEntities.MembershipInformation _result = ((MyDataEntities.MembershipInformation)(base.EndInvoke("RegisterUser", _args, result)));
                      return _result;
                  }
                   catch (Exception ex)
                  {
                      MyDataEntities.MembershipInformation _result = new MyDataEntities.MembershipInformation();
                      _result.ValidationInformation.HasErrors = true;
                      _result.ValidationInformation.Message = ex.Message;
                      return _result;
                  }
              }
          }
          

          【讨论】:

          • 为什么要做所有这些努力,只是为了丢弃除 Message 属性之外的所有异常部分?丢弃信息几乎从来都不是一个好主意。
          • 约翰所说的。 StackTrace 和 InnerException 特别有用。
          【解决方案6】:

          使用 Silverlight 3,Visual Studio 调试器会捕获这些异常,因此永远无法到达异常处理程序(令人困惑的是)。但是,当运行调试器时,异常处理程序会按预期调用。我想这没问题,只要有人意识到这一点。我承认我浪费了几个小时试图弄清楚如何深入了解 Silverlight/Wcf/Browser 的内部工作以解决我的异常。不要去那里。

          【讨论】:

            【解决方案7】:

            哎呀……

            对不起,我的回答错误(好吧,MSFT 的人没有点击写答案服务回调在同一个 UI 线程上调用),事情是

            更多信息:

            - In development even detaching from the debugger, this method is never reached. 
            - On the production environment yes.
            

            我的猜测与 Visual Studio 选项和拦截异常有关。

            更多信息,在这个线程中 http://silverlight.net/forums/p/48613/186745.aspx#186745

            相当有趣的话题。

            【讨论】:

              【解决方案8】:

              您可以忘记异步客户端回调中的 Application_UnhandledException,原因是:

              Application_UnhandledException 只有在 UI 线程上触发的异常才能被 Application.UnhandledExceptions 捕获

              这意味着...根本不会为 WCF 异步调用调用 :-)。

              查看 MSFT 的详细回复

              http://silverlight.net/forums/t/21828.aspx

              您好,只有在 UI 线程上触发的异常才能被 Application.UnhandledExceptions 捕获。它无法捕获来自其他线程的异常。您可以尝试这样做来解决问题:在 Visual Studio 中,从“调试”菜单中选择“异常”。然后检查“公共语言运行时异常”。这将使调试器在抛出异常时停止。但是请注意,这有时可能会很烦人,因为即使已经捕获了异常。您可以使用 CheckBoxes 过滤您想要捕获的异常。

              就我而言,好消息是,如果您不进行调试,只需在客户端服务回调中处理错误消息就足够了。

              谢谢

              Braulio
              

              【讨论】:

              • 事实并非如此。当异常源自 WCF 服务时,我肯定会遇到 Application_UnhandledExceptions。我可以说出来,因为我可以轻松检查抛出的 FaultException。
              【解决方案9】:

              我发现a forum thread 正在讨论这个问题,它提到最佳实践是使用 Error 属性。在这个话题和我自己的经历之间,我可以得出以下结论:

              • 在普通 .NET 代码中,生成的代理类通过将异常放入 Error 属性而不是抛出它来正确处理异常。

              • 在 Silverlight 中,生成的代理类设置 Error 属性,但不完全处理异常。异常被调试器拾取,它会弹出异常框,并显示消息“ProtocolException 未被用户代码处理”。尽管有此消息,但该异常似乎并未真正出现在 Application_UnhandledException 函数中。

              我希望这是他们将在最终版本中修复的问题之一。

              现在,我将使用 Error 属性并只处理调试器中断执行。如果太烦人,我可以关闭 ProtocolException 的异常中断。

              【讨论】:

                猜你喜欢
                • 2012-10-10
                • 2010-12-04
                • 2013-01-01
                • 2022-08-03
                • 2021-08-07
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多