Утилизировать окно WPF

У меня есть окно WPF, которое я показываю с помощью Window.Show(). Когда я нажимаю X, форма закрывается. Но он все еще находится в памяти, и GC никогда не приходит и не очищает его. Также у меня нет ссылок/дескрипторов.

Кто-нибудь знает, что может заставить его остаться в памяти?

Спасибо!

Обновление: я использую следующий код для создания окна:

   Public Sub OpenPageWindow(ByVal nick As String)
        Dim found As Boolean

        For Each pw As PageWindow In Application.Current.Windows.OfType(Of PageWindow)()
            If Not pw.Tag Is Nothing Then
                If pw.Tag.ToString() = nick Then
                    If pw.IsVisible = False Then
                        pw.Show()
                    End If
                    Exit Sub
                End If
            End If
        Next

        If found = False Then
            Dim p As New PageWindow With {.Name = "pw" & nick, .Tag = nick, _
                                          .Title = "Chatting with " & nick, .ShowActivated = False}
            p.Show()
        End If
    End Sub

и следующий код для самого окна:

Public Class PageWindow
    Implements System.IDisposable

    Public UserPressedExit As Boolean
    Dim MainWin As MainWindow

    Private _IsBuddy As Boolean
    Private _IsBlocked As Boolean
    Private _IsOnline As Boolean

    Public Property FirstOpen As Boolean

    Public Property IsOnline As Boolean
        Get
            Return _IsOnline
        End Get
        Set(ByVal value As Boolean)
            If _IsOnline <> value Then
                _IsOnline = value
            End If
        End Set
    End Property

    Public Property IsBuddy As Boolean
        Get
            Return _IsBuddy
        End Get
        Set(ByVal value As Boolean)
            If _IsBuddy <> value Then
                _IsBuddy = value
                'If value Then
                '    btnAddBuddy.IsEnabled = False
                '    iDeleteBuddy.IsEnabled = True
                'Else
                '    btnAddBuddy.IsEnabled = True
                '    iDeleteBuddy.IsEnabled = False
                'End If
            End If
        End Set
    End Property

    Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        Try
            MainWin = CType(Application.Current.Windows(0), MainWindow)
        Catch ex As Exception
            txtChat.AppendText("ERROR: " & ex.Message.ToString() & Environment.NewLine)
        End Try
    End Sub

    Private Sub Window_Closed(ByVal sender As System.Object, ByVal e As System.EventArgs)
        Me.Dispose()
    End Sub

    Private Sub txtTitle_PreviewMouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
        Me.DragMove()
    End Sub

    Private Sub pbMin_PreviewMouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
        WindowState = Windows.WindowState.Minimized
    End Sub

    Private Sub pbClose_PreviewMouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
        UserPressedExit = True
        txtChat.Clear()
        Me.Close()
    End Sub

    Private Sub txtSend_KeyUp(ByVal sender As System.Object, ByVal e As System.Windows.Input.KeyEventArgs)
        If e.Key = Key.Enter Then
            btnSend_Click(Nothing, Nothing)
        End If
    End Sub

    Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
        If String.IsNullOrWhiteSpace(txtSend.Text) Then
            MessageBox.Show("Empty Message.")
            txtSend.Text = ""
        Else
            Send(txtSend.Text)

            txtSend.Text = ""
        End If
    End Sub

    Public Sub Page(ByVal message As String)
        If String.IsNullOrEmpty(message) = False Then
            If MainWin.Settings.ShowPageTimestamp Then
                txtChat.AppendText(Me.Tag.ToString() & " (" & Format(Date.Now, "HH:mm:ss") & "): " & message & Environment.NewLine)
            Else
                txtChat.AppendText(Me.Tag.ToString() & ": " & message & Environment.NewLine)
            End If

            If Me.IsFocused = False Then

            End If
        End If
    End Sub

    Private Sub Send(ByVal text As String)
        'work on 389 event
        MainWin.Send("PAGE " + Me.Tag.ToString + " " + text)

        If MainWin.Settings.ShowPageTimestamp Then
            txtChat.AppendText(MainWin.Settings.Nick & ": " & text & Environment.NewLine)
        Else
            txtChat.AppendText(MainWin.Settings.Nick & " (" & Format(Date.Now, "HH:mm:ss") & "): " & text & Environment.NewLine)
        End If
    End Sub

#Region "IDisposable Support"
    Private disposed As Boolean ' To detect redundant calls

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposed Then
            If disposing Then
                'dispose managed state (managed objects).
                RemoveHandler Me.Loaded, AddressOf Window_Loaded
                RemoveHandler txtTitle.PreviewMouseDown, AddressOf txtTitle_PreviewMouseDown
                RemoveHandler pbMin.PreviewMouseUp, AddressOf pbMin_PreviewMouseUp
                RemoveHandler pbClose.PreviewMouseUp, AddressOf pbClose_PreviewMouseUp
                RemoveHandler txtSend.KeyUp, AddressOf txtSend_KeyUp
                RemoveHandler btnSend.Click, AddressOf btnSend_Click
                RemoveHandler Me.Closed, AddressOf Window_Closed

                BindingOperations.ClearBinding(txtTitle, TextBlock.TextProperty)

                Me.Icon = Nothing
                ibBackground = Nothing
                'iDeleteBuddy = Nothing
                'btnAddBuddy = Nothing
                'iBlockUser = Nothing
                pbMin = Nothing
                pbClose = Nothing
                MainWin = Nothing

                Me.Resources.Clear()

                GC.Collect()
            End If
        End If
        Me.disposed = True
    End Sub

    Protected Overrides Sub Finalize()
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(False)
        MyBase.Finalize()
    End Sub
#End Region
End Class

Затем следующий код для XAML:

<Window x:Class="PageWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="PageWindow" Height="300" Width="400"
        Icon="Images\R2.ico" WindowStyle="None" ResizeMode="NoResize"
        WindowStartupLocation="Manual" Loaded="Window_Loaded"
        BorderThickness="2" BorderBrush="#FFAE28"
        Closed="Window_Closed">
    <Window.Resources>
        <Style x:Key="sRenegadeButton" TargetType="Button">
            <Setter Property="FontFamily" Value="Franklin Gothic Medium" />
            <Setter Property="FontSize" Value="12px" />
            <Setter Property="FontWeight" Value="Bold" />
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="Foreground" Value="#FFAE28" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Name="Border" BorderBrush="#FFAE28" BorderThickness="1" CornerRadius="3" 
                                Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                            <Border Name="innerborder" BorderBrush="#FFAE28" BorderThickness="1" CornerRadius="3" 
                                    Background="{TemplateBinding Background}" SnapsToDevicePixels="True" Margin="1">
                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Name="content" />
                            </Border>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Border" Property="BorderBrush" Value="#FFD528" />
                                <Setter TargetName="innerborder" Property="BorderBrush" Value="#FFD528" />
                                <Setter Property="Background" Value="#28FFAE28" />
                                <Setter Property="Foreground" Value="#FFD528" />
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter Property="Background" Value="#28FFAE28" />
                                <Setter TargetName="content" Property="RenderTransform" >
                                    <Setter.Value>
                                        <TranslateTransform Y="1.0" />
                                    </Setter.Value>
                                </Setter>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter TargetName="Border" Property="BorderBrush" Value="#80E6A023" />
                                <Setter TargetName="innerborder" Property="BorderBrush" Value="#80E6A023" />
                                <Setter Property="Foreground" Value="#8CFFD528" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style x:Key="sRenegadeTextBox" TargetType="TextBox">
            <Setter Property="FontFamily" Value="Franklin Gothic Medium" />
            <Setter Property="FontSize" Value="12px" />
            <Setter Property="Foreground" Value="#FFD528" />
            <Setter Property="Background" Value="#28FFAE28" />
            <Setter Property="BorderBrush" Value="#FFAE28" />
            <Setter Property="CaretBrush" Value="#FFAE28" />
            <Setter Property="SelectionBrush" Value="#FFD528" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TextBox">
                        <Border Name="Bd" BorderThickness="{TemplateBinding BorderThickness}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
                            <ScrollViewer Name="PART_ContentHost" Background="{TemplateBinding Background}" 
                                          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Foreground" Value="#8CFFD528" />
                                <Setter Property="Background" Value="#1EFFAE28" />
                                <Setter Property="BorderBrush" Value="#80E6A023" />
                                <Setter TargetName="PART_ContentHost" Property="Background" Value="#1EFFAE28"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="21" />
            <RowDefinition Height="32" />
            <RowDefinition Height="*" />
            <RowDefinition Height="27" />
        </Grid.RowDefinitions>

        <Grid.Background>
            <ImageBrush x:Name="ibBackground"  ImageSource="Images\page_back.gif" />
        </Grid.Background>

        <Border Grid.Row="0" BorderBrush="#FFAE28" BorderThickness="0,0,0,2" 
                SnapsToDevicePixels="True" />

        <Grid Name="gTitleBar" Grid.Row="0" Background="#32FFAE28">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="30" />
                <ColumnDefinition Width="32" />
            </Grid.ColumnDefinitions>

            <TextBlock Name="txtTitle" Grid.Column="0" Text="{Binding Title,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}"
                       HorizontalAlignment="Stretch" VerticalAlignment="Center"
                       TextAlignment="Left" Margin="4,0,1,0" 
                       PreviewMouseDown="txtTitle_PreviewMouseDown"
                       Foreground="#FED528" FontFamily="Franklin Gothic Medium" />

            <Image Name="pbMin" Grid.Column="1" Width="28" Height="15" 
                    HorizontalAlignment="Center" VerticalAlignment="Center"
                    PreviewMouseUp="pbMin_PreviewMouseUp" Stretch="None">
                <Image.Style>
                    <Style TargetType="{x:Type Image}">
                        <Setter Property="Source" Value="Images\min.png" />

                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Source" Value="Images\min_o.png" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Image.Style>
            </Image>
            <Image Name="pbClose" Grid.Column="2" Width="28" Height="15" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    PreviewMouseUp="pbClose_PreviewMouseUp" Stretch="None">
                <Image.Style>
                    <Style TargetType="{x:Type Image}">
                        <Setter Property="Source" Value="Images\close2.png" />

                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Source" Value="Images\close2_o.png" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Image.Style>
            </Image>
        </Grid>

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="25" />
                <ColumnDefinition Width="25" />
                <ColumnDefinition Width="25" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!--<Button Name="btnAddBuddy" Grid.Column="1" Margin="0,2,0,0">
                <Button.Style>
                    <Style TargetType="{x:Type Button}">
                        <Setter Property="Background" Value="Transparent" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type Button}">
                                    <Grid>
                                        <Border Name="Border" BorderBrush="Transparent" BorderThickness="1" CornerRadius="3" 
                                            Background="{TemplateBinding Background}" SnapsToDevicePixels="True">

                                        </Border>
                                        <Image Name="iAddBuddy" Margin="1" Source="Images\Add Buddy 32x36.png" />
                                        <Image Name="iAddBuddyDissabled" Margin="1" Source="Images\Add Buddy 32x36.png" Visibility="Hidden" />
                                    </Grid>

                                    <ControlTemplate.Triggers>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter TargetName="Border" Property="BorderBrush" Value="#FFD528" />
                                        </Trigger>
                                        <Trigger Property="IsPressed" Value="True">
                                            <Setter Property="Background" Value="#FFD528" />
                                        </Trigger>
                                        <Trigger Property="IsEnabled" Value="False">
                                            <Setter TargetName="Border" Property="BorderBrush" Value="Transparent" />
                                            <Setter TargetName="iAddBuddy" Property="Visibility" Value="Hidden" />
                                            <Setter TargetName="iAddBuddyDissabled" Property="Visibility" Value="Visible" />
                                        </Trigger>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Button.Style>
            </Button>

            <Border Name="bDeleteBuddyBorder" Grid.Column="2" BorderThickness="1"
                    SnapsToDevicePixels="True" Margin="0,2,0,0">
                <Image Name="iDeleteBuddy" Margin="1" IsEnabled="True"
                   Source="Images\Delete 32x32.png">
                    <Image.Style>
                        <Style TargetType="{x:Type Image}">
                            <Style.Triggers>
                                <Trigger Property="IsEnabled" Value="False">
                                    <Setter Property="Source" Value="Images\Delete Disabled 32x32.png" />
                                </Trigger>
                            </Style.Triggers>
                        </Style>
                    </Image.Style>
                </Image>
                <Border.Style>
                    <Style TargetType="Border">
                        <Setter Property="BorderBrush" Value="Transparent" />
                        <Setter Property="Background" Value="Transparent" />
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="BorderBrush" Value="#FFD528" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Border.Style>
            </Border>

            <Border Name="bBlockUserBorder" Grid.Column="3" BorderThickness="1"
                    SnapsToDevicePixels="True" Margin="0,2,0,0">
                <Image Name="iBlockUser" Margin="1" IsEnabled="True"
                       Source="Images\Block Buddy 32x36.png">
                    <Image.Style>
                        <Style TargetType="{x:Type Image}">
                            <Style.Triggers>
                                <Trigger Property="IsEnabled" Value="False">
                                    <Setter Property="Source" Value="Images\Block Buddy Dissabled 32x36.png" />
                                </Trigger>
                            </Style.Triggers>
                        </Style>
                    </Image.Style>
                </Image>
                <Border.Style>
                    <Style TargetType="Border">
                        <Setter Property="BorderBrush" Value="Transparent" />
                        <Setter Property="Background" Value="Transparent" />
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="BorderBrush" Value="#FFD528" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Border.Style>
            </Border>-->
        </Grid>

        <TextBox Name="txtChat" Grid.Row="2" Margin="4,2,4,2"
                 Style="{StaticResource sRenegadeTextBox}" UndoLimit="0" />

        <Grid Grid.Row="3">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="45" />
            </Grid.ColumnDefinitions>

            <TextBox Name="txtSend" Grid.Column="0" MaxLength="495" 
                     Height="22" Margin="4,1,2,4" VerticalContentAlignment="Center" 
                     Style="{StaticResource sRenegadeTextBox}" UndoLimit="0"
                     KeyUp="txtSend_KeyUp" />
            <Button Name="btnSend" Content="Send" Grid.Column="1" 
                    Style="{StaticResource sRenegadeButton}" 
                    Height="22" Width="40" Margin="0,1,4,4" Click="btnSend_Click" />
        </Grid>
    </Grid>
</Window>

По сути, даже если я создаю пустое окно WPF с фоновым изображением для основной сетки и двумя обработчиками событий Opening и Closed, оно все равно остается в памяти, но когда все «пустые окна wpf» закрываются, память только немного уменьшается. немножко. Почему от простых окон не избавляются полностью? Я даже пытался реализовать удаленную функциональность, закодированную выше.


person tcables    schedule 05.12.2010    source источник
comment
Откуда ты знаешь, что это все еще в памяти?   -  person svick    schedule 05.12.2010
comment
Я нажимаю кнопку, которая выполняет windowname.show(), затем в окне я нажимаю кнопку X. и память никогда не меняется в диспетчере задач. и он делает это для каждого окна, сделанного впредь.   -  person tcables    schedule 05.12.2010


Ответы (4)


Что так же важно, как и память, если у вас текут дескрипторы - в процессах (в диспетчере задач) добавьте столбец для объектов GDI и дескрипторов.

Когда вы открываете и закрываете окно, оно будет увеличиваться и уменьшаться - если у вас нет утечки - с утечкой приходит некоторая память. Я бы не слишком беспокоился о том, что некоторые kb не исчезнут сразу, они в конечном итоге будут очищены или использованы повторно, но дескрипторы должны исчезнуть, чтобы они могли быть хорошим признаком.

Обычно утечки возникают при использовании статических обработчиков событий или других статических структур, которые содержат ссылку на элемент управления или окно, то есть статические словари или хеш-таблицы. Если ссылка необходима, не забудьте очистить ее при закрытии или использовать WeakReference.

Методы Dispose нужны только тогда, когда у вас есть ресурсы в качестве переменных-членов в вашем окне/классе, и они должны быть реализованы правильно (ваши выглядят правильно, но необязательно :) - вы также должны убедиться, что Dispose() действительно вызывается. .

person Rune Andersen    schedule 14.12.2010
comment
Я бы удалил весь код удаления - в нем нет необходимости. Есть ли у вас какие-либо ссылки на ваши окна страниц из главного окна? Когда окно закрывается, оно очищается — поэтому, если вы видите, что количество дескрипторов для вашего приложения уменьшается, у вас нет проблем — выделенная память будет освобождена в какой-то момент. - person Rune Andersen; 15.12.2010
comment
Нет необходимости в dispose()? Даже для мероприятия? в основном первая часть кода вверху — это все, что я использую для управления окном страницы. Этот и другие потоки используют делегат для вызова в главном окне для доступа к этому подразделу. Сегодня проверю ручки, спасибо. - person tcables; 15.12.2010
comment
Я начинаю с 300 дескрипторов и 38 объектов GDI с использованием 17 КБ памяти. Я открываю 10 окон страниц, и он увеличивается до 350 дескрипторов и 90 объектов gdi с использованием 25 КБ памяти. Я закрываю все окна, и диспетчер задач показывает 368 дескрипторов, 38 gdi и 24 КБ используемой памяти. зачем столько ручек? - person tcables; 16.12.2010
comment
Вы получаете дескриптор для каждого используемого вами системного ресурса - окно обычно имеет 3 дескриптора, контекстное меню имеет дескрипторы, файлы, значки, таймеры, шрифты и т. д. В winforms каждый элемент управления имеет дескриптор и является gdiobject. Похоже, ваши окна освобождены, а память и дескрипторы ждут своего часа - мне это не кажется дырявым. И вообще очень мало памяти. - person Rune Andersen; 16.12.2010
comment
Если вы хотите быть уверены, что сборщик мусора заработает, вы можете выделить большой кусок памяти в каждом окне - тогда вы должны увидеть, как он освобождается, когда вы закрываете окно. Профилировщик памяти Redgates — отличный инструмент, но это все еще детективная работа по поиску утечки :) Явный вызов GC.Collect, насколько я помню, не рекомендуется. - person Rune Andersen; 16.12.2010
comment
Я считаю, что всплеск памяти был вызван модулями ленивой загрузки .Net для обработки моих окон. Например, процессор изображений для фона, который у меня был для формы, или другие модули для обработки различных элементов управления в формах. - person tcables; 30.12.2010

Прежде всего, убедитесь, что проблема с памятью именно такая, как вы думаете (т. е. не очищается), используя реальный инструмент памяти, такой как профилировщик памяти Redgate, у которого есть бесплатная пробная версия (http://www.red-gate. com/products/ants_memory_profiler/).

Затем, если вы все еще сталкиваетесь с проблемой, профилировщик выделит то, что говорит сборщику мусора не очищать ее (ссылки других объектов и тому подобное).

На первый взгляд, я бы сказал, что это могут быть обработчики событий, которые вы подключили, попробуйте отменить подписку на них, как только они закончатся.

Кроме того, как указано в другом месте, вызовите GC.Collect в качестве дополнительной проверки.

person Mark    schedule 10.12.2010
comment
В коде, который я предоставил, я использую removehandler для всех оконных событий. - person tcables; 11.12.2010

Кто-нибудь знает, что может заставить его остаться в памяти?

Прислушивается ли ваше окно к каким-либо внешним событиям? Если это так, использование слабого диспетчера событий может решить вашу проблему: http://msdn.microsoft.com/en-us/library/aa970850.aspx.

person janemann    schedule 05.12.2010
comment
внешние события из других окон? нет. - person tcables; 05.12.2010
comment
у него есть события, связанные с самой формой ... но я использую обработчик удаления для каждого, но окно не будет удаляться. - person tcables; 13.12.2010

Вы абсолютно уверены, что среда выполнения гарантированно выполнит сборку мусора в таком сценарии?

Я думаю, вполне возможно, что GC не видит необходимости в сборке мусора в этом случае. Убедитесь, вызвав GC.Collect в своем коде, и посмотрите, изменит ли это ситуацию.

person Jon    schedule 10.12.2010
comment
В коде, который я предоставил, у меня есть GC.Collect в защищенном переопределяемом подразделе Dispose (ByVal disposeing As Boolean). - person tcables; 11.12.2010