1. 程式人生 > >WPF WebBrowser Memory Leak 問題及臨時解決方法

WPF WebBrowser Memory Leak 問題及臨時解決方法

exce step lose rpe quest 不同 程序啟動 round collect

首先介紹一下內存泄漏(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

WPF WebBrowser Memory Leak 問題及臨時解決方法