1. 程式人生 > >C#反射呼叫WebService引起記憶體洩漏

C#反射呼叫WebService引起記憶體洩漏

最近寫了一個小工具,用來定時檢測公司各臺伺服器上的WebService是否工作正常.如果無法訪問則報警.

開發思路也很簡單, 設定一個Timer, 定時啟動多個執行緒(每個執行緒負責N臺伺服器訪問任務)去動態訪問各伺服器上的WebService的指定方法. 然後對異常資訊進行報警.

動態訪問WebService技術採用大家都熟悉的反射(Reflection), 如下:

/// < summary>          
/// 動態呼叫WebService
/// < /summary>          
/// < param name="url">WSDL服務地址< /param>
/// < param name="classname">類名< /param>  
/// < param name="methodname">方法名< /param>  
/// < param name="args">引數< /param> 
/// < returns>< /returns>
public static object InvokeWebService(string url, string classname, string methodname, object[] args)
{
    string @namespace = "EnterpriseServerBase.WebService.DynamicWebCalling";
    if ((classname == null) || (classname == ""))
    {
        classname = WSHelperReflection.GetWsClassName(url);
    }
    try
    {                   
        //獲取WSDL   
        WebClient wc = new WebClient();
        Stream stream = wc.OpenRead(url + "?WSDL");
        ServiceDescription sd = ServiceDescription.Read(stream);
        ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
        sdi.AddServiceDescription(sd, "", "");
        CodeNamespace cn = new CodeNamespace(@namespace);
        //生成客戶端代理類程式碼
        CodeCompileUnit ccu = new CodeCompileUnit();
        ccu.Namespaces.Add(cn);
        sdi.Import(cn, ccu);
                
        CSharpCodeProvider icc = new CSharpCodeProvider();
        //設定編譯引數
        CompilerParameters cplist = new CompilerParameters();
        cplist.GenerateExecutable = false;
        cplist.GenerateInMemory = true;
        cplist.ReferencedAssemblies.Add("System.dll");
        cplist.ReferencedAssemblies.Add("System.XML.dll");
        cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
        cplist.ReferencedAssemblies.Add("System.Data.dll");
        //編譯代理類
        CompilerResults cr = icc.CompileAssemblyFromDom(cplist, ccu);
        if (true == cr.Errors.HasErrors)
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
            {
                sb.Append(ce.ToString());
                sb.Append(System.Environment.NewLine);
            }
            throw new Exception(sb.ToString());
        }
        //生成代理例項,並呼叫方法
        System.Reflection.Assembly assembly = cr.CompiledAssembly;
        Type t = assembly.GetType(@namespace + "." + classname, true, true);
        object obj = Activator.CreateInstance(t);
        System.Reflection.MethodInfo mi = t.GetMethod(methodname);
        return mi.Invoke(obj, args);
    }
    catch (Exception ex)
    {
        throw new Exception(ex.InnerException.Message, new Exception(ex.InnerException.StackTrace));
    }
}

完成編碼並簡單測試通過後就投入了使用.大約8小時後發現Run這個工具的電腦出現 記憶體不足 的報警並導致檢測工具Crash !

用dotMemory檢測發現, 每一輪呼叫訪問就會產生大量Unmanaged Memory 未被釋放(即:  記憶體洩漏)

圖1: 灰色區塊中包含了Unmanaged memory


圖2: 灰色區塊就是 Unmanaged memory


問題應該出在反射上!

因為通過反射載入到記憶體中的DLL只有在關閉程式時才會被釋放, 而這個工具會一直開著並且定時使用反射去動態呼叫WebService, 這樣就會不斷佔用記憶體直到資源耗盡.

我曾嘗試在呼叫完成後把能釋放的一切資源都釋放掉但沒有任何幫助. 對於這種反射的情況,微軟似乎也沒有提供一個很好的辦法.

後記: 由於目前沒有找到解決方案, 最終放棄使用動態呼叫WebService改為呼叫固定方法(Method), 然後在初始化時依次替換訪問伺服器的IP來實現檢測任務. 

改用此方案後, 再未出現 記憶體洩漏 的問題.