8

.NET / WCF / Windowsサービスでメモリとハンドルのリークを探していると、説明できない奇妙な動作に気づきました。ここにセットアップと解像度があります。私が探しているのは、観察された行動の説明です。

Windowsサービスをインストールしました。
サービスを開始しました。
トランザクションWCF呼び出しを使用して単純なメソッドを呼び出しました(呼び出しごとに新しいチャネル-キャッシュなし)。
呼び出しごとに、約2つのハンドルがメモリに残ります。

これは、次の項目が該当する場合に観察できます。

  1. これはWindowsサービスです。コンソールアプリとして実行しないでください。
  2. トランザクション(個別のプロセスまたはマシンでテスト済みのみ)を使用して、WCFメソッドを呼び出します。
  3. ServiceBase.Run(servicesToRun);あるタイプでインスタンス化XmlSerializerを呼び出す前。
  4. タイプはカスタムタイプです。new XmlSerializer(typeof(string))またはnewでは発生しませんXmlSerializer(typeof(XmlDocument))。シリアル化するための呼び出しは必要ありません。カスタムタイプにプロパティとして文字列しかない場合は十分です(ハンドルはどこにもありません!)
  5. つまり、SGen.exeを使用して静的XmlSerialization.dllを作成しても、この問題は発生しません。

私のコードにはすでに修正が含まれています: OnStart()で最も早く
XmlSerializerを使用します:

Program.cs

WindowsService winSvc = new WindowsService();
ServiceBase[] servicesToRun = new ServiceBase[]{winSvc};                    
ServiceBase.Run(servicesToRun);

WindowsService.cs

internal sealed class WindowsService : ServiceBase
{
    private ServiceHost wcfServiceHost = null;

    internal WindowsService()
    {
        AutoLog = true;
        CanStop = true;
        CanShutdown = true;
        CanPauseAndContinue = false;
    }

    internal void StartWcfService()
    {
        wcfServiceHost = new ServiceHost(typeof(DemoService));
        wcfServiceHost.Open();
    }

    protected override void Dispose(bool disposing)
    {
        if (wcfServiceHost != null)
        {
            wcfServiceHost.Close();
        }

        base.Dispose(disposing);
    }

    protected override void OnStart(string[] args)
    {
        new XmlSerializer(typeof(MyType));

        StartWcfService();
    }
}

DemoService.cs

[ServiceBehavior
    (
        InstanceContextMode = InstanceContextMode.PerSession,
        TransactionAutoCompleteOnSessionClose = false,
        IncludeExceptionDetailInFaults = true
    )
]
public sealed class DemoService : IDemoService
{           
    [TransactionFlow(TransactionFlowOption.Allowed)]
    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
    public int Add(int a, int b)
    {
        return a + b;
    }
}

Client.cs

IChannelFactory<IDemoService> channelFactory = new ChannelFactory<IDemoService>("defaultClientConfiguration");
IDisposable channel = null;
for (int index = 0; index < 5000; index++)
{
    using
    (
        channel = (IDisposable)channelFactory.CreateChannel(new EndpointAddress("net.tcp://localhost:23456/DemoService")))
        {                       
        IDemoService demoService = (IDemoService)channel;
        using (TransactionScope tx = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            demoService.Add(3, 9);
            tx.Complete();  
        }
    )
}

誰かがこの振る舞いを説明できますか?

注意してください、私はリークを回避する方法を見つけることに興味がありません(私はすでにこれを行う方法を知っています)が、説明(つまり、なぜそれが起こっているのか)に興味があります。

4

2 に答える 2

7

内部の仕組みのいくつかは、この問題を正当化していると思います。少し前にこの問題に遭遇したので、頭の後ろからこれを行います.ReflectorとANTS Memoryプロファイラーの広範な使用を含めて追跡するのに1日かかりました(私の前の会社で)... ここ行きます:

XML シリアライザーが内部で行うことは、渡された型を受け入れる System.Reflection.Emit を使用してクラス ('A' と呼びましょう) を作成することです。このようなクラスの構築には、相対的に言えば多くの時間がかかり、型が変わらないため再利用可能です。このため、構築された型はディクショナリに格納されます。たとえば、何らかの Dictionary が作成されます。

既知の (基本的な) 型の場合、シリアライザ コードは固定されています。たとえば、アプリケーションを何度再起動しても、文字列のシリアライズは変更されません。最初に XMLSerializer に渡されるまでシリアライゼーション ファクトリに認識されない型である 'A' との違いに注意してください。

型が XMLSerializer によって初めて使用されるとき、このプロセスは、渡された型と必要なすべての型 (たとえば、シリアル化が必要なすべてのフィールドとプロパティ) の両方に対して行われます。

リークについて... ChannelFactory を呼び出すと、シリアライザーがまだ存在しない場合は構築されます。そのために、シリアライザーが辞書に既に存在するかどうかを確認し、存在しない場合は、ISomeSerializerType のインスタンスを作成して作成します。

なんらかの馬鹿げた理由で、辞書に保存せずに新しいシリアライザーを構築するバグがファクトリーに存在します。いったん構築されると、オブジェクトが正しく破棄されていても、新しい型になります。これはリークとして表示されます (型は決してアンロードできないことに注意してください)。最初に XMLSerializer を使用するか、静的クラスを作成すると、ディクショナリ キャッシュが正しく使用されるため、リークは発生しません。それで、それはバグです。以前はANTS Memory Profilerにアクセスできましたが、これは非常にうまく表示されました.

これが説明することを願っています。

于 2013-01-09T19:37:47.907 に答える
6

XmlSerializer のドキュメントには、次のように書かれています。

パフォーマンスを向上させるために、XML シリアル化インフラストラクチャはアセンブリを動的に生成して、指定された型をシリアル化および逆シリアル化します。インフラストラクチャは、それらのアセンブリを見つけて再利用します。この動作は、次のコンストラクターを使用する場合にのみ発生します。

XmlSerializer.XmlSerializer(タイプ)

XmlSerializer.XmlSerializer(型, 文字列)

他のコンストラクターを使用すると、同じアセンブリの複数のバージョンが生成され、アンロードされないため、メモリ リークが発生し、パフォーマンスが低下します。最も簡単な解決策は、前述の 2 つのコンストラクターのいずれかを使用することです。それ以外の場合は、次の例に示すように、アセンブリを Hashtable にキャッシュする必要があります。

http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx

于 2012-10-26T12:28:00.410 に答える