一:文起緣由
寫這一篇的目的源自於最近看同事在寫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 }
好了,綜合我上面所說的一切,我個人覺得最好的方式應該是上面這樣,夜深了,睡覺了,晚安。