加载中...

第七天 Close和Abort到底该怎么用才对得起观众


一:文起缘由

          写这一篇的目的源自于最近看同事在写wcf的时候,用特别感觉繁琐而且云里雾里的嵌套try catch来防止client抛出异常,特别感觉奇怪,就比如下面的代码。

看完上面的代码,不知道你是否有什么感想?而且我还问了同事,为什么try catch要写成这样,同事说是根据什么书上来的什么最佳实践,这话一说,我也不敢轻易

怀疑了,只能翻翻源代码看看这话是否有道理,首先我来说说对这段代码的第一感觉。。。

1. 代码特别繁琐

我们写代码,特别不喜欢繁琐,上面的代码就是一例,你try catch就try catch,还在finally中嵌套一个try catch,真的有点感觉像吃了两只癞蛤蟆一样。。。

2. 混淆close和abort的用法

这种代码给人的感觉就是为什么不精简一下呢???比如下面这样,起码还可以少写一对try catch,对吧。

而且乍一看这段代码和文中开头那一段代码貌似实现一样,但是某些人的“最佳实践”却不是这样,所以确实会导致我这样的后来人犯迷糊,对吧。。。反正我就是头晕,

简直就是弄糊涂到什么时候该用close,什么时候该用abort。。。

二:探索原理

为了弄明白到底可不可以用一个try catch来替代之,下面我们一起研究一下。

1.  从代码注释角度甄别

从类库的注释中,可以比较有意思的看出,abort方法仅仅比close多一个“立即”,再无其他,有意思,不过这对我来说并没有什么卵用,因为这个注释太

笼统了,为了让自己更加彻底的明白,只能来翻看下close和abort的源代码。

2.  从源码角度甄别

为了方便让ILSpy调试Client代码,现在我决定用ChannelFactory来代替,如下图:

为了让大家更好的理解,我把close方法的源码提供如下:

  1. // System.ServiceModel.Channels.CommunicationObject
  2. [__DynamicallyInvokable]
  3. public void Close(TimeSpan timeout)
  4. {
  5. if (timeout < TimeSpan.Zero)
  6. {
  7. throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("timeout", SR.GetString("SFxTimeoutOutOfRange0")));
  8. }
  9. using ((DiagnosticUtility.ShouldUseActivity && this.TraceOpenAndClose) ? this.CreateCloseActivity() : null)
  10. {
  11. CommunicationState communicationState;
  12. lock (this.ThisLock)
  13. {
  14. communicationState = this.state;
  15. if (communicationState != CommunicationState.Closed)
  16. {
  17. this.state = CommunicationState.Closing;
  18. }
  19. this.closeCalled = true;
  20. }
  21. switch (communicationState)
  22. {
  23. case CommunicationState.Created:
  24. case CommunicationState.Opening:
  25. case CommunicationState.Faulted:
  26. this.Abort();
  27. if (communicationState == CommunicationState.Faulted)
  28. {
  29. throw TraceUtility.ThrowHelperError(this.CreateFaultedException(), Guid.Empty, this);
  30. }
  31. goto IL_174;
  32. case CommunicationState.Opened:
  33. {
  34. bool flag2 = true;
  35. try
  36. {
  37. TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
  38. this.OnClosing();
  39. if (!this.onClosingCalled)
  40. {
  41. throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosing"), Guid.Empty, this);
  42. }
  43. this.OnClose(timeoutHelper.RemainingTime());
  44. this.OnClosed();
  45. if (!this.onClosedCalled)
  46. {
  47. throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosed"), Guid.Empty, this);
  48. }
  49. flag2 = false;
  50. goto IL_174;
  51. }
  52. finally
  53. {
  54. if (flag2)
  55. {
  56. if (DiagnosticUtility.ShouldTraceWarning)
  57. {
  58. TraceUtility.TraceEvent(TraceEventType.Warning, 524292, SR.GetString("TraceCodeCommunicationObjectCloseFailed", new object[]
  59. {
  60. this.GetCommunicationObjectType().ToString()
  61. }), this);
  62. }
  63. this.Abort();
  64. }
  65. }
  66. break;
  67. }
  68. case CommunicationState.Closing:
  69. case CommunicationState.Closed:
  70. goto IL_174;
  71. }
  72. throw Fx.AssertAndThrow("CommunicationObject.BeginClose: Unknown CommunicationState");
  73. IL_174:;
  74. }
  75. }

然后我提供一下Abort代码:

仔细观察完这两个方法,你会发现什么呢???至少我可以提出下面四个问题:

1:Abort是Close的子集吗?

 是的,因为如果你看懂了Close,你会发现Close只针对Faulted 和Opened做了判断,而其中在Faulted的枚举下会调用原生的Abort方法。。。如下图

2:我能监视Client的各种状态吗?比如Created,Opening,Fault,Closed等等。。。

当然可以了,wcf的信道老祖宗就是ICommunicationObject,而它就有5种监听事件,这些就可以随时监听,懂伐???

3:Abort会抛出异常吗?

从这个截图中可以看到非常有意思的一段,那就是居然abort活生生的把异常给吞了。。。骨头都不给吐出来。。。真tmd的神奇到家了,想想也有道理,因为只有

这样,我们上层的代码在catch中才不会二次抛出“未处理异常”了,对吧,再转念看一下Close方法。

 

从上面图中可以看到,Close在遇到Faulted之后调用Abort方法,如果说Abort方法调用失败,Close方法会再次判断状态,如果还是Faulted的话,就会向上抛出

异常。。。这就是为什么Abort不会抛异常,Close会的原因,所以Close千万不要放在Catch块中。

4. Abort代码大概都干了些什么

这个问题问的好,要能完美解决的话,我们看下代码,如下图,从图中可以看到,Abort的大目的就是用来关闭信道,具体会经过closeing,abort和closed这

三个方法,同时,这三个事件也会被老祖宗ICommunicationObject监听的到。

 

好了,最后我们关注的一个问题在于下面这条语句是否应该放在Try块中???

  1. ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));

很简单,我们简要的看一下代码,看里面是否会有“异常”抛出即可。。。。

可以看到,在new的过程中可能,或许会有异常的产生,所以最好把try catch改成下面这样。。。


还没有评论.