私は Windows 7、.NET 4.5、IE10 を使用しています。コントロールを持つ単純な Windows フォーム アプリケーションがありますWebBrowser
。ページが移動するたびに、クリック ハンドラーを追加したいと考えています。これを行うと、メモリがリークしていることに気付きました。私は実際にイベント ハンドラーをデタッチしているので、なぜメモリ リークが発生するのか混乱しています。
HtmlDocument.Click
イベントにアタッチでき、 に関連するリークが表示されないことに気付きましたHtmlElement.Click
。これは、タイマーを使用して定期的にナビゲートする以下のプログラムで実証されています。
また、興味深いのは、URL が重要であるように思われることです...たとえば、http://www.google.com には問題がありますが、http://thin.npr.org (または about:blank) には問題がありません。これにより、これは根本的にJavaScriptの問題であると信じるようになりますが、現在採用している「修正」(以下を参照)は実際にはJavaScriptとは何の関係もありません...
では、問題の原因と、プログラムがメモリ/ハンドルをリークしないように問題を回避するにはどうすればよいですか?
私の現在の「修正」はMarshal.ReleaseComObject
、HtmlElement.DomElement
プロパティを呼び出すことです。これは正しいことですか?どうやらそうHtmlElement
であるように、それ自体を片付けるべきではありませんか?HtmlDocument
に加えて、他のクラスの問題を予期する必要がありHtmlElement
ますか?
Marshal.ReleaseComObject
私は、com オブジェクトが見えるところならどこにでも惜しみなく振りかけたいと思っています (それを含むクラスが適切にそうするとは限りません)。それは安全なことですか?
私の完全なプログラムは次のとおりです。
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public class Form1 : Form
{
Timer t;
WebBrowser webBrowser;
public Form1()
{
webBrowser = new WebBrowser
{
Dock = DockStyle.Fill,
Url = new Uri("http://www.google.com/")
};
webBrowser.DocumentCompleted += webBrowser_DocumentCompleted;
this.Controls.Add(webBrowser);
t = new Timer { Interval = 2000 };
t.Tick += t_Tick;
}
void t_Tick(object sender, EventArgs e)
{
t.Stop();
webBrowser.Navigate("http://www.google.com/");
}
void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (webBrowser.ReadyState == WebBrowserReadyState.Complete)
{
var doc = webBrowser.Document;
if (doc == null) //no page loaded
return;
//the following two lines do not cause a leak
doc.Click += click_handler;
doc.Click -= click_handler;
var body = doc.Body;
if (body != null)
{
//the following 2 lines cause memory and handle leaks
body.Click += click_handler;
body.Click -= click_handler;
//uncommenting the following line appears to fix the leak
//Marshal.ReleaseComObject(body.DomElement);
body = null;
}
GC.Collect(2, GCCollectionMode.Forced);
t.Start();
}
}
void click_handler(object sender, HtmlElementEventArgs e)
{
}
}
}
好奇心旺盛な人のために、プログラム内WeakReference
のオブジェクトに a を使用してテストを行いbody
、それがガベージ コレクションされていることを確認しました。
「修正済み」バージョンをしばらく (おそらく 1 時間) 実行した後、約 40 MB のプライベート バイトと 640 ハンドルで安定しています。「リーキー」バージョンは、最大で数百メガバイト、数千ハンドルになり、速度が低下しているようには見えません。本番環境では、機能が停止したとき (画像が読み込まれない)、900 MB を超えるプロセスを使用していたユーザーがいました。アプリケーションを閉じると、すべてが破棄されるまでに数秒かかりました。