上一篇強調及時關閉服務代理(Close())對釋放會話資源的重要性。但現實是:當服務端拋出異常時,代理進入 Faulted 狀態,直接呼叫 Close() 會再次拋出 CommunicationObjectFaultedException。本文深入剖析原因,並給出生產級的異常處理最佳實踐。
使用 WSHttpBinding(預設啟用會話),服務端拋出 DivideByZeroException。客戶端 finally 區塊呼叫 Close() 時拋出異常:
CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
Opened→Faulted。異常導致通道進入 Faulted,後續任何操作(含 Close())皆失敗。WSHttpBinding 預設 SecurityMode.Message → 啟用會話。若設為 SecurityMode.None 則為資料報通道,Close() 不會失敗(但也失去會話優勢)。
針對會話通道,必須捕獲通訊異常並呼叫 Abort() 強制釋放資源:
using (var factory = new ChannelFactory<ICalculator>("calculateservice"))
{
ICalculator proxy = factory.CreateChannel();
try
{
int result = proxy.Divide(1, 0); // 觸發異常
(proxy as ICommunicationObject).Close(); // 正常路徑
}
catch (CommunicationException ex)
{
(proxy as ICommunicationObject).Abort(); // 強制關閉
// 記錄日誌、重試邏輯...
throw;
}
catch (TimeoutException ex)
{
(proxy as ICommunicationObject).Abort();
throw;
}
catch (Exception ex)
{
// 其他業務異常,通道未必 Faulted,嘗試 Close
(proxy as ICommunicationObject).Close();
}
}
利用委派封裝通用異常處理邏輯:
static TReturn Invoke<TContract, TReturn>(TContract proxy, Func<TContract, TReturn> func)
{
TReturn ret = default;
try
{
ret = func(proxy);
(proxy as ICommunicationObject).Close();
}
catch (CommunicationException)
{
(proxy as ICommunicationObject).Abort(); throw;
}
catch (TimeoutException)
{
(proxy as ICommunicationObject).Abort(); throw;
}
catch (Exception)
{
(proxy as ICommunicationObject).Close(); // 非通訊異常嘗試正常關閉
}
return ret;
}
// 使用極簡潔
int result = Invoke(proxy, p => p.Divide(2, 1));
同理提供 Action 版本供無返回值操作。
Abort()。CommunicationException、TimeoutException 並 Abort 是鐵律。Invoke 輔助方法,統一錯誤處理,提升代碼品質。