## 一:文起緣由
? ? ? ? ? 寫這一篇的目的源自于最近看同事在寫wcf的時候,用特別感覺繁瑣而且云里霧里的嵌套try catch來防止client拋出異常,特別感覺奇怪,就比如下面的代碼。
~~~
1 public void StartNormalMarketing(int shopId, List<int> marketingIdList)
2 {
3
4 using (SendEventMarketingService.DistributeServiceClient client = new SendEventMarketingService.DistributeServiceClient())
5 {
6 try
7 {
8
9 client.StartByMarketingIDList(shopId, marketingIdList, SendEventMarketingService.MarketingType.NormalMarketing);
10
11 }
12 catch (Exception ex)
13 {
14 LogHelper.WriteLog("常規營銷活動開啟服務", ex);
15 }
16 finally
17 {
18 try
19 {
20 client.Close();
21 }
22 catch (Exception)
23 {
24 client.Abort();
25 }
26 }
27 }
28 }
~~~
看完上面的代碼,不知道你是否有什么感想?而且我還問了同事,為什么try catch要寫成這樣,同事說是根據什么書上來的什么最佳實踐,這話一說,我也不敢輕易
懷疑了,只能翻翻源代碼看看這話是否有道理,首先我來說說對這段代碼的第一感覺。。。
1\. 代碼特別繁瑣
我們寫代碼,特別不喜歡繁瑣,上面的代碼就是一例,你try catch就try catch,還在finally中嵌套一個try catch,真的有點感覺像吃了兩只癩蛤蟆一樣。。。
2\. 混淆close和abort的用法
這種代碼給人的感覺就是為什么不精簡一下呢???比如下面這樣,起碼還可以少寫一對try catch,對吧。
~~~
1 public void StartNormalMarketing(int shopId, List<int> marketingIdList)
2 {
3
4 using (SendEventMarketingService.DistributeServiceClient client = new SendEventMarketingService.DistributeServiceClient())
5 {
6 try
7 {
8
9 client.StartByMarketingIDList(shopId, marketingIdList, SendEventMarketingService.MarketingType.NormalMarketing);
10
11 client.Close();
12 }
13 catch (Exception ex)
14 {
15 LogHelper.WriteLog("常規營銷活動開啟服務", ex);
16
17 client.Abort();
18 }
19 }
20 }
~~~
而且乍一看這段代碼和文中開頭那一段代碼貌似實現一樣,但是某些人的“最佳實踐”卻不是這樣,所以確實會導致我這樣的后來人犯迷糊,對吧。。。反正我就是頭暈,
簡直就是弄糊涂到什么時候該用close,什么時候該用abort。。。
## 二:探索原理
為了弄明白到底可不可以用一個try catch來替代之,下面我們一起研究一下。
1\. ?從代碼注釋角度甄別
從類庫的注釋中,可以比較有意思的看出,abort方法僅僅比close多一個“立即”,再無其他,有意思,不過這對我來說并沒有什么卵用,因為這個注釋太
籠統了,為了讓自己更加徹底的明白,只能來翻看下close和abort的源代碼。

2\. ?從源碼角度甄別
為了方便讓ILSpy調試Client代碼,現在我決定用ChannelFactory來代替,如下圖:
~~~
1 namespace ConsoleApplication1
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>();
8
9 try
10 {
11 var channel = factory.CreateChannel();
12
13 factory.Close();
14 }
15 catch (Exception ex)
16 {
17 factory.Abort();
18 }
19 }
20 }
21 }
~~~
為了讓大家更好的理解,我把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 // System.ServiceModel.Channels.CommunicationObject
2 [__DynamicallyInvokable]
3 public void Abort()
4 {
5 lock (this.ThisLock)
6 {
7 if (this.aborted || this.state == CommunicationState.Closed)
8 {
9 return;
10 }
11 this.aborted = true;
12 this.state = CommunicationState.Closing;
13 }
14 if (DiagnosticUtility.ShouldTraceInformation)
15 {
16 TraceUtility.TraceEvent(TraceEventType.Information, 524290, SR.GetString("TraceCodeCommunicationObjectAborted", new object[]
17 {
18 TraceUtility.CreateSourceString(this)
19 }), this);
20 }
21 bool flag2 = true;
22 try
23 {
24 this.OnClosing();
25 if (!this.onClosingCalled)
26 {
27 throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosing"), Guid.Empty, this);
28 }
29 this.OnAbort();
30 this.OnClosed();
31 if (!this.onClosedCalled)
32 {
33 throw TraceUtility.ThrowHelperError(this.CreateBaseClassMethodNotCalledException("OnClosed"), Guid.Empty, this);
34 }
35 flag2 = false;
36 }
37 finally
38 {
39 if (flag2 && DiagnosticUtility.ShouldTraceWarning)
40 {
41 TraceUtility.TraceEvent(TraceEventType.Warning, 524291, SR.GetString("TraceCodeCommunicationObjectAbortFailed", new object[]
42 {
43 this.GetCommunicationObjectType().ToString()
44 }), this);
45 }
46 }
47 }
~~~
仔細觀察完這兩個方法,你會發現什么呢???至少我可以提出下面四個問題:
1:Abort是Close的子集嗎?
?是的,因為如果你看懂了Close,你會發現Close只針對Faulted 和Opened做了判斷,而其中在Faulted的枚舉下會調用原生的Abort方法。。。如下圖

2:我能監視Client的各種狀態嗎?比如Created,Opening,Fault,Closed等等。。。
當然可以了,wcf的信道老祖宗就是ICommunicationObject,而它就有5種監聽事件,這些就可以隨時監聽,懂伐???

~~~
1 static void Main(string[] args)
2 {
3 ChannelFactory<IHomeService> factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));
4
5 try
6 {
7 factory.Opened += (o, e) =>
8 {
9 Console.WriteLine("Opened");
10 };
11
12 factory.Closing += (o, e) =>
13 {
14 Console.WriteLine("Closing");
15 };
16
17 factory.Closed += (o, e) =>
18 {
19 Console.WriteLine("Closed");
20 };
21
22 var channel = factory.CreateChannel();
23
24 var result = channel.Update(new Student() { });
25
26 factory.Close();
27 }
28 catch (Exception ex)
29 {
30 factory.Abort();
31 }
32 }
~~~
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改成下面這樣。。。
~~~
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 ChannelFactory<IHomeService> factory = null;
6 try
7 {
8 factory = new ChannelFactory<IHomeService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:1920/HomeServie"));
9
10 var channel = factory.CreateChannel();
11
12 var result = channel.Update(new Student() { });
13
14 factory.Close();
15
16 throw new Exception();
17 }
18 catch (Exception ex)
19 {
20 if (factory != null)
21 factory.Abort();
22 }
23 }
24 }
~~~
好了,綜合我上面所說的一切,我個人覺得最好的方式應該是上面這樣,夜深了,睡覺了,晚安。
- 第一天 三種Binding讓你KO80%的業務
- 第二天 告別煩惱的config配置
- 第三天 client如何知道server提供的功能清單
- 第四天 你一定要明白的通信單元Message
- 第五天 你需要了解的三個小技巧
- 第六天 你必須要了解的3種通信模式
- 第七天 Close和Abort到底該怎么用才對得起觀眾
- 第八天 對“綁定”的最后一點理解
- 第九天 高級玩法之自定義Behavior
- 第十天 學會用SvcConfigEditor來簡化配置
- 第十一天 如何對wcf進行全程監控
- 第十二天 說說wcf中的那幾種序列化
- 第十三天 用WCF來玩Rest
- 第十四天 一起聊聊FaultException
- 終結篇 那些你需要注意的坑