1. 程式人生 > >十五天精通WCF——第七天 Close和Abort到底該怎麼用才對得起觀眾

十五天精通WCF——第七天 Close和Abort到底該怎麼用才對得起觀眾

 

一:文起緣由

          寫這一篇的目的源自於最近看同事在寫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     }

好了,綜合我上面所說的一切,我個人覺得最好的方式應該是上面這樣,夜深了,睡覺了,晚安。