首先介绍一下内存泄漏(Memory Leak)的概念,内存泄露是指程序中已动态分配的堆内存由于某种原因未释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
最近在使用WPF WebBrowser时,就遇到了Memory Leak的问题。
在主窗体上通过一个按钮点击事件加载包含有WebBrowser控件的窗体,使用这个WebBrowser来浏览网页,然后调用WebBrowser的Dispose()方法,然后调用GC.Collect(),最后关闭当前包含有WebBrowser控件的窗体。
通过下面的代码和步骤来还原这个问题。
MainWindow.xaml
<StackPanel Orientation="Horizontal" VerticalAlignment="Top"> <Button Content="Launch Browser Window" x:Name="btnLaunchNewWindow" Margin="5,0,5,0" Click="btnLaunchNewWindow_Click" /> <Button Content="Force Garbage Collection" x:Name="btnForceGarbageCollection" Click="btnForceGarbageCollection_Click" /> <Button Content="Quit" x:Name="btnQuit" Click="btnQuit_Click" /> </StackPanel>
private void btnLaunchNewWindow_Click(object sender, RoutedEventArgs e) { new BrowserWindow().Show(); } private void btnForceGarbageCollection_Click(object sender, RoutedEventArgs e) { System.GC.Collect(); System.GC.WaitForPendingFinalizers(); System.GC.Collect(); } private void btnQuit_Click(object sender, RoutedEventArgs e) { Environment.Exit(0); }
BrowserWindow.xaml
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <Label Content="URL:" /> <TextBox x:Name="txtURL" Width="400" /> </StackPanel> <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,10"> <Button Content="1. Go/Navigate" x:Name="btnGo" Click="btnGo_Click" /> <Button Content="2. Dispose" x:Name="btnClose" Click="btnClose_Click" Margin="10,0" /> <Button Content="3. Force Garbage Collection" x:Name="btnForceGarbageCollection" Click="btnForceGarbageCollection_Click" /> <Button Content="4. Close Window" x:Name="closeWindow" Click="closeWindow_Click" Margin="10,0" /> </StackPanel> <WebBrowser Grid.Row="2" x:Name="webBrowser" /> </Grid>
private void btnGo_Click(object sender, RoutedEventArgs e) { try { webBrowser.Navigate(new Uri(txtURL.Text)); } catch (Exception ex) { MessageBox.Show("Exception: " + ex.Message); } } private void btnClose_Click(object sender, RoutedEventArgs e) { webBrowser.Dispose(); } private void btnForceGarbageCollection_Click(object sender, RoutedEventArgs e) { System.GC.Collect(); System.GC.WaitForPendingFinalizers(); System.GC.Collect(); } private void closeWindow_Click(object sender, RoutedEventArgs e) { this.Close(); }
问题重现步骤:
Step1: 启动程序
Step2: “Launch Browser Window”
Step3: 在地址栏输入 http://www.msn.com (其他网址也可以)
Step4: 点击“1. Go/Navigate” button,
Step5: 当网页加载成功后,点击 “2. Dispose”
Step6: 点击 ”3. Force Garbage Collection”
Step7: 点击“4. Close Window”
重复Step2--Step7 20-25次。
多次测试后的结果如下:
在Step1程序启动后,内存占用在20M左右(不同的机器会有一些差别),
重复Step2--Step7 20-25次之后,程序的内存在130M左右,并且长时间等待不释放。
很不幸运的遇到一个内存泄露的问题。
和大多数WPF控件不一样,WebBrowser控件继承自HwndHost,使用的是非托管的资源,所以对WebBrowser进行Dispose()操作并不管用。
第一种解决方法:
早前使用过WinForm的WebBrowser控件,不存在内存泄露的问题,所以决定使用WinForm的WebBrowser代替WPF的。关于如何在WPF中承载WinForm控件,请参考https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/walkthrough-hosting-a-windows-forms-control-in-wpf
第二种解决方法:
依然使用WPF WebBrowser,但是将第二个页面(BrowserWindow.xaml)单独成一个exe。通过主程序去调用,这样当网页浏览完毕后,关闭WebBrowser所在exe,它所关联的内存全部被释放掉了。如果两个程序之间需要通信或者交换数据,可以选用WCF/命名管道等方式。
参考链接:
https://stackoverflow.com/questions/2069314/memory-leak-when-using-wpf-webbrowser-control-in-multiple-windows
https://stackoverflow.com/questions/8302933/how-to-get-around-the-memory-leak-in-the-net-webbrowser-control