WCF(Windows Communication Foundation)以其極高的擴展性著稱,無論是在信道層還是服務模型層,開發者都能透過自訂元件注入運行時環境。在眾多擴展點中,ICallContextInitializer 允許我們在服務操作執行前後插入邏輯,實質上是一種 AOP(面向切面編程)的實現方式。例如,它可用於確保服務端與客戶端共享相同的語言文化,或自動傳遞上下文資訊。其介面定義如下:
public interface ICallContextInitializer
{
void AfterInvoke(object correlationState);
object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message);
}
然而,若在 AfterInvoke 中拋出未處理的異常,將導致整個服務宿主進程終止——這是一個極其嚴重的問題。本文將重現該問題、分析根因並給出總結建議。
為了驗證此行為,我們定義一個自訂的 MyCallContextInitializer,在 AfterInvoke 中直接拋出異常:
public class MyCallContextInitializer : ICallContextInitializer
{
public void AfterInvoke(object correlationState)
{
throw new Exception("調用 MyCallContextInitializer.AfterInvoke() 出錯!");
}
public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
{
return null;
}
}
接著透過實作 IServiceBehavior 的 MyServiceBehaviorAttribute,將該初始化器註冊到所有端點的分發運行時操作中:
public class MyServiceBehaviorAttribute : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher endpoint in dispatcher.Endpoints)
{
foreach (DispatchOperation operation in endpoint.DispatchRuntime.Operations)
{
operation.CallContextInitializers.Add(new MyCallContextInitializer());
}
}
}
}
// 其他介面方法省略
}
將此行為套用至簡單的計算器服務 CalculatorService,並在 Console 應用程式中託管。當客戶端呼叫服務時,宿主進程會直接崩潰。事件檢視器中會記錄兩筆關鍵日誌,來源分別為 System.ServiceModel 3.0.0.0 與 .NET Runtime。值得注意的是,日誌訊息錯誤地顯示為 ICallContextInitializer.BeforeInvoke threw an exception...,實際上應為 AfterInvoke——這是 WCF 內部的一個小 Bug。

透過 Reflector 反編譯 WCF 原始碼,可發現 AfterInvoke 最終由 DispatchOperationRuntime.UninitializeCallContextCore 私有方法呼叫:
private void UninitializeCallContextCore(ref MessageRpc rpc)
{
try
{
for (int i = this.CallContextInitializers.Length - 1; i >= 0; i--)
{
this.CallContextInitializers[i].AfterInvoke(rpc.Correlation[callContextCorrelationOffset + i]);
}
}
catch (Exception exception)
{
DiagnosticUtility.FailFast(string.Format(CultureInfo.InvariantCulture,
"ICallContextInitializer.BeforeInvoke threw an exception of type {0}: {1}",
exception.GetType(), exception.Message));
}
}
所有 AfterInvoke 呼叫包裹在 try/catch 中,一旦捕獲異常,即呼叫 DiagnosticUtility.FailFast。進一步查看 FailFast 實現:
[MethodImpl(MethodImplOptions.NoInlining)]
internal static Exception FailFast(string message)
{
try
{
try { ExceptionUtility.TraceFailFast(message); }
finally { Environment.FailFast(message); }
}
catch { }
Environment.FailFast(message);
return null;
}
Environment.FailFast 會立即終止當前進程,並不執行任何 finally 區塊或最終化程序。這正是導致服務宿主非正常退出的根本原因。此外,錯誤訊息中寫死的 BeforeInvoke 字串,造成了事件日誌的誤導。
CallContextInitializer 雖然提供了強大的 AOP 能力,但 AfterInvoke 必須保證不拋出任何未處理異常。開發者應在該方法內部妥善處理所有可能的錯誤(如交易提交失敗),並透過記錄日誌等方式上報,而非讓異常向上傳播。若需在操作完成後執行關鍵清理邏輯,建議改用 IDispatchMessageInspector 或操作行為等替代擴展點,並配合完善的異常處理機制。