首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 擴展點 ICallContextInitializer 導致服務宿主崩潰的深度剖析

     編輯:關於.NET

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。

CommunicationException TimeoutException

二、根因剖析

透過 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 或操作行為等替代擴展點,並配合完善的異常處理機制。

出處:artech.cnblogs.com

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved