.NET Webbrowser 컨트롤에서 메모리 누수를 피하는 방법은 무엇입니까?
이것은 .NET Webbrowser 컨트롤에서 널리 알려진 오래된 문제입니다.
요약 : .NET 웹 브라우저 컨트롤이있는 페이지로 이동하면 해제되지 않는 메모리 사용량이 증가합니다.
메모리 누수 재현 : WebBrowser 컨트롤을 폼에 추가하십시오. 그것을 사용하여 원하는 페이지로 이동하십시오. about : blank 작동합니다. 사용량이 100MB 이상이 될 때까지 Google 이미지에서 아래로 스크롤 한 다음 다른 곳을 탐색하여 해당 메모리가 거의 확보되지 않은 것을 알아 차리는 것이 더 극적인 데모입니다.
응용 프로그램에 대한 현재 요구 사항에는 제한된 IE7 브라우저 창을 표시하는 오랜 기간 동안 응용 프로그램을 실행하는 것이 포함됩니다. 훅, BHO 및 그룹 정책의 일부 나쁜 설정으로 IE7 자체를 실행하는 것도 바람직하지 않지만 현재로서는 폴백처럼 보입니다. Windows Forms 애플리케이션에 브라우저를 포함하는 것은 다음과 같습니다. 다른 브라우저 기반을 사용하는 것은 나에게 제공되는 옵션이 아닙니다. IE7이 필요합니다.
이 알려진 메모리 누수와 관련된 이전 스레드 및 기사 :
- http://www.vbforums.com/showthread.php?t=644658
- IE WebBrowser 컨트롤에서 메모리 누수를 수정하는 방법?
- 여러 창에서 WPF WebBrowser 컨트롤을 사용할 때 메모리 누수
- http://social.msdn.microsoft.com/Forums/en-US/ieextensiondevelopment/thread/88c21427-e765-46e8-833d-6021ef79e0c8/
- http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/8a2efea4-5e75-4e3d-856f-b09a4e215ede
- http://dotnetforum.net/topic/17400-appdomain-webbrowser-memory-leak/
작동하지 않는 자주 제안 된 수정 :
- 다른 페이지로 이동하는 것은 중요하지 않습니다. about : blank는 누출을 유발합니다. 자바 스크립트 또는 기타 추가 기술이있는 페이지가 필요하지 않습니다.
- 다른 버전의 Internet Explorer를 사용하는 것은 중요하지 않습니다. 7, 8 및 9는 모두 동일한 증상을 나타내며 내가 들었던 한 모든 버전은 컨트롤에서 동일한 메모리 누수가 발생합니다.
- 컨트롤의 Dispose () ing은 도움이되지 않습니다.
- 가비지 수집은 도움이되지 않습니다. (사실이 조사에 따르면 Webbrowswer 컨트롤이 래핑하는 관리되지 않는 COM 코드에 누수가 있음을 나타냅니다.)
- 프로세스 사용 가능한 메모리를 최소화하고 -1, -1 (SetProcessWorkingSetSize () 또는 simimlar.)로 설정하면 실제 메모리 사용량이 줄어들뿐 가상 메모리에는 영향을주지 않습니다.
- WebBrowser.Stop ()을 호출하는 것은 해결책이 아니며, 누수를 최소화하는 것 이상을 수행하지 않고 정적 웹 페이지 이외의 것을 사용하는 기능을 중단합니다.
- 다른 문서로 이동하기 전에 문서가 완전히로드 될 때까지 기다리도록하는 것도 도움이되지 않습니다.
- 별도의 appDomain에 컨트롤을로드해도 문제가 해결되지 않습니다. (내가 직접 해본 것은 아니지만 연구에 따르면 다른 사람들이이 경로로 성공하지 못한 것으로 나타났습니다.)
- csexwb2와 같은 다른 래퍼를 사용하면 동일한 문제가 발생하기 때문에 도움이되지 않습니다.
- 임시 인터넷 파일 캐시를 지우면 아무 작업도 수행되지 않습니다. 문제는 디스크가 아닌 활성 메모리에 있습니다.
전체 응용 프로그램을 닫고 다시 시작하면 메모리가 지워집니다.
문제에 대한 확실한 해결책이라면 COM 또는 Windows API에서 직접 브라우저 컨트롤을 작성할 의향이 있습니다. 물론 덜 복잡한 수정을 선호합니다. 나는 브라우저의 지원되는 기능 측면에서 바퀴를 재발 명하고 싶지 않기 때문에 일을하기 위해 더 낮은 수준으로 내려가는 것을 피하고 싶다. 독자적인 스타일 브라우저에서 IE7 기능과 비표준 동작을 복제하는 Letalone.
도움?
이 누수는 관리되지 않는 메모리의 누수로 보이므로 프로세스에서 수행하는 작업은 해당 메모리를 회수하지 않습니다. 귀하의 게시물에서 나는 당신이 유출을 상당히 광범위하고 성공하지 못하도록 피하려고 노력했음을 알 수 있습니다.
가능한 경우 다른 접근 방식을 제안합니다. 웹 브라우저 제어를 사용하는 별도의 애플리케이션을 만들고 애플리케이션에서 시작합니다. 여기 에 설명 된 방법을 사용하여 기존 응용 프로그램 내에 새로 만든 응용 프로그램을 포함합니다. WCF 또는 .NET Remoting을 사용하여 해당 애플리케이션과 통신합니다. 많은 메모리를 차지하지 않도록 수시로 하위 프로세스를 다시 시작하십시오.
물론 이것은 매우 복잡한 솔루션이며 재시작 프로세스는 아마도 추악 해 보일 수 있습니다. 사용자가 다른 페이지로 이동할 때마다 전체 브라우저 애플리케이션을 다시 시작하는 방법을 사용할 수 있습니다.
나는 udione 의 코드를 가져 와서 (감사합니다!) 두 가지 작은 것을 변경했습니다.
IKeyboardInputSite 는 공용 인터페이스이며 Unregister () 메서드가 있으므로 * _keyboardInputSinkChildren * 컬렉션에 대한 참조를받은 후 리플렉션을 사용할 필요가 없습니다.
뷰에 항상 창 클래스 (특히 MVVM)에 대한 직접 참조가있는 것은 아니므로 시각적 트리를 통과하여 필요한 참조를 반환하는 GetWindowElement (DependencyObject 요소) 메서드를 추가했습니다 .
감사합니다, udione
public void Dispose()
{
_browser.Dispose();
var window = GetWindowElement(_browser);
if (window == null)
return;
var field = typeof(Window).GetField("_swh", BindingFlags.NonPublic | BindingFlags.Instance);
var valueSwh = field.GetValue(window);
var valueSourceWindow = valueSwh.GetType().GetField("_sourceWindow", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(valueSwh);
var valuekeyboardInput = valueSourceWindow.GetType().GetField("_keyboardInputSinkChildren", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(valueSourceWindow);
var inputSites = valuekeyboardInput as IEnumerable<IKeyboardInputSite>;
if (inputSites == null)
return;
var currentSite = inputSites.FirstOrDefault(s => ReferenceEquals(s.Sink, _browser));
if (currentSite != null)
currentSite.Unregister();
}
private static Window GetWindowElement(DependencyObject element)
{
while (element != null && !(element is Window))
{
element = VisualTreeHelper.GetParent(element);
}
return element as Window;
}
모두 감사합니다!
리플렉션을 사용하고 mainForm의 개인 필드에서 참조를 제거하여 메모리 누수를 제거하는 방법이 있습니다. 이것은 좋은 해결책은 아니지만 절망적 인 사람들을위한 코드는 다음과 같습니다.
//dispose to clear most of the references
this.webbrowser.Dispose();
BindingOperations.ClearAllBindings(this.webbrowser);
//using reflection to remove one reference that was not removed with the dispose
var field = typeof(System.Windows.Window).GetField("_swh", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var valueSwh = field.GetValue(mainwindow);
var valueSourceWindow = valueSwh.GetType().GetField("_sourceWindow", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(valueSwh);
var valuekeyboardInput = valueSourceWindow.GetType().GetField("_keyboardInputSinkChildren", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(valueSourceWindow);
System.Collections.IList ilist = valuekeyboardInput as System.Collections.IList;
lock(ilist)
{
for (int i = ilist.Count-1; i >= 0; i--)
{
var entry = ilist[i];
var sinkObject = entry.GetType().GetField("_sink", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (object.ReferenceEquals(sinkObject.GetValue(entry), this.webbrowser.webBrowser))
{
ilist.Remove(entry);
}
}
}
이 정확한 앓은 데 아웃 메모리의 서로 다른 방향에서 문제를 ( Win32에서은 WorkingSet / COM SHDOCVW 인터페이스 등 )에와 WPF의WebBrowser
우리가 있었다위한 구성 요소, 나는이 문제를 발견 있는 jqGrid 익스플로러에서 관리되지 않는 리소스 잡고 플러그인을 ActiveXHost
호출 한 후이를 해제하지 WebBrowser.Dispose()
. 이 문제는 종종 제대로 작동하지 않는 Javascript에 의해 생성됩니다. 이상한 점은 Javascript가 일반 IE에서 잘 작동한다는 것입니다 WebBrowser
. IE를 실제로 닫을 수 없기 때문에 두 통합 지점간에 가비지 수집이 다르다고 생각합니다.
소스 페이지를 작성하는 경우 제가 제안하는 한 가지는 모든 JS 구성 요소를 제거하고 천천히 다시 추가하는 것입니다. 문제가되는 JS 플러그인 ( 우리가 한 것처럼 ) 을 식별하면 문제를 쉽게 해결할 수 있습니다. 우리의 경우 우리 $("#jqgrid").jqGrid('GridDestroy')
는 이벤트와 그것이 생성 한 관련 DOM 요소를 적절히 제거하는 데 사용 했습니다. 이것은 브라우저가를 통해 닫힐 때 이것을 호출함으로써 우리를 위해 문제를 해결했습니다 WebBrowser.InvokeScript
.
탐색하는 소스 페이지를 수정할 수없는 경우 페이지에 JS를 주입하여 메모리 누수를 일으키는 DOM 이벤트와 요소를 정리해야합니다. Microsoft는이에 대한 해결책을 찾을 수 있다면 그것은 좋은 것입니다,하지만 지금 우리는 필요 JS 플러그인 프로빙 왼쪽되어 정화 .
아래 솔루션이 저에게 효과적이었습니다.
Protected Sub disposeBrowers()
If debug Then debugTrace()
If Me.InvokeRequired Then
Me.Invoke(New simple(AddressOf disposeBrowers))
Else
Dim webCliffNavigate As String = webCliff.Url.AbsoluteUri
Me.DollarLogoutSub()
If dollarLoggedIn Then
Exit Sub
End If
'Dim webdollarNavigate As String = webDollar.Url.AbsoluteUri
Me.splContainerMain.SuspendLayout()
Me.splCliffDwellers.Panel2.Controls.Remove(webCliff)
Me.splDollars.Panel2.Controls.Remove(webDollar)
RemoveHandler webCliff.DocumentCompleted, AddressOf webCliff_DocumentCompleted
RemoveHandler webDollar.DocumentCompleted, AddressOf webDollar_DocumentCompleted
RemoveHandler webCliff.GotFocus, AddressOf setDisposeEvent
RemoveHandler webCliff.LostFocus, AddressOf setDisposeEvent
RemoveHandler webDollar.GotFocus, AddressOf setDisposeEvent
RemoveHandler webDollar.LostFocus, AddressOf setDisposeEvent
webCliff.Stop()
webDollar.Stop()
Dim tmpWeb As SHDocVw.WebBrowser = webCliff.ActiveXInstance
System.Runtime.InteropServices.Marshal.ReleaseComObject(tmpWeb)
webCliff.Dispose()
tmpWeb = webDollar.ActiveXInstance
System.Runtime.InteropServices.Marshal.ReleaseComObject(tmpWeb)
webDollar.Dispose()
tmpWeb = Nothing
webCliff = Nothing
webDollar = Nothing
GC.AddMemoryPressure(50000)
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForFullGCComplete()
GC.Collect()
GC.RemoveMemoryPressure(50000)
webCliff = New WebBrowser()
webDollar = New WebBrowser()
webCliff.CausesValidation = False
webCliff.Dock = DockStyle.Fill
webDollar.CausesValidation = webCliff.CausesValidation
webDollar.Dock = webCliff.Dock
webDollar.ScriptErrorsSuppressed = True
webDollar.Visible = True
webCliff.Visible = True
Me.splCliffDwellers.Panel2.Controls.Add(webCliff)
Me.splDollars.Panel2.Controls.Add(webDollar)
Me.splContainerMain.ResumeLayout()
'AddHandler webCliff.DocumentCompleted, AddressOf webCliff_DocumentCompleted
'AddHandler webDollar.DocumentCompleted, AddressOf webDollar_DocumentCompleted
'AddHandler webCliff.GotFocus, AddressOf setDisposeEvent
'AddHandler webCliff.LostFocus, AddressOf setDisposeEvent
'AddHandler webDollar.GotFocus, AddressOf setDisposeEvent
'AddHandler webDollar.LostFocus, AddressOf setDisposeEvent
webCliff.Navigate(webCliffNavigate)
disposeOfBrowsers = Now.AddMinutes(20)
End If
End Sub
Best of luck, Layla
I think this question has gone unanswered for a long time now. So many threads with the same question but not conclusive answer.
I have found a work around for this issue and wanted to share with you all who are still facing this issue.
step1: Create a new form say form2 and add a web-browser control on it. step2: In the form1 where you have your webbrowser control, just remove it. step3: Now, go to Form2 and make the access modifier for this webbrowser control to be public so that it can be accessed in Form1 step4: Create a panel in form1 and create object of form2 and add this into panel. Form2 frm = new Form2 (); frm.TopLevel = false; frm.Show(); panel1.Controls.Add(frm); step5: Call the below code at regular intervals frm.Controls.Remove(frm.webBrowser1); frm.Dispose();
Thats it. Now when you run it, you can see that webbrowser control loaded and it will get disposed at regular intervals and there is no more hanging of the application.
You can add the below code to make it more efficient.
IntPtr pHandle = GetCurrentProcess();
SetProcessWorkingSetSize(pHandle, -1, -1);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Believe its more of an .Net Framework issue rather than Web Browser control. Hooking to the navigated event of the browser with a handler which will dispose the browser and then navigating to about:blank will be a good workaround. For example:
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
var browser = (WebBrowser) _stackPanel.Children[_stackPanel.Children.Count - 1];
_stackPanel.Children.RemoveAt(_stackPanel.Children.Count-1);
NavigatedEventHandler dispose = null;
dispose = (o, args) =>
{
browser.Navigated -= dispose;
browser.Dispose();
};
browser.Navigated += dispose;
browser.Navigate(new Uri("about:blank"));
}
Try this solution, i know its not ideal. Paste the code after each page load
System.Diagnostics.Process loProcess = System.Diagnostics.Process.GetCurrentProcess();
try
{
loProcess.MaxWorkingSet = (IntPtr)((int)loProcess.MaxWorkingSet - 1);
loProcess.MinWorkingSet = (IntPtr)((int)loProcess.MinWorkingSet - 1);
}
catch (System.Exception)
{
loProcess.MaxWorkingSet = (IntPtr)((int)1413120);
loProcess.MinWorkingSet = (IntPtr)((int)204800);
}
ReferenceURL : https://stackoverflow.com/questions/8302933/how-to-get-around-the-memory-leak-in-the-net-webbrowser-control
'program story' 카테고리의 다른 글
CQRS : 명령 반환 값 (0) | 2020.12.26 |
---|---|
C ++ 로깅 프레임 워크 제안 (0) | 2020.12.26 |
Python Pandas로 들어오는 실시간 데이터를 처리하는 방법 (0) | 2020.12.26 |
git-subtree 추가 후 rebase하는 방법은 무엇입니까? (0) | 2020.12.26 |
스칼라 : 함수 인수에서 튜플 분해 (0) | 2020.12.25 |