2011-07-01 12 views
6

Tengo una aplicación wpf simple. En la ventana principal tengo panel de pila y 2 botones. El primer botón agrega 100 mis controles de usuario (sin ningún enlace de datos, eventos, mapas de bits), y el segundo los elimina del panel y llama a GC.Collect(). Y hay algunos problemas: 1. Después de hacer clic en el botón "Eliminar" la primera vez, no todas las versiones de mi memoria se liberan, y debo hacer clic varias veces para liberar más memoria. 2. Después de 5 - 10 minutos de liberación de memoria pero pocos megabytes no.Fugas de memoria WPF

por ejemplo, después de que mi aplicación se inicia se tarda ~ 22MB cuando adición de 500 controles - ~ 60mb después de que hace clic en "eliminar" botón primera vez - ~ 55 MB (espero algún tiempo, la memoria no cancela la asignación) yo haga clic en unos pocos los tiempos y la memoria se redujo a 25mb, No entiendo esto, soy nuevo en WPF, y tal vez echo de menos algo Quiero liberar memoria de inmediato.

<Window x:Class="WpfApplication10.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="MainWindow" Height="385" Width="553"> 
<Grid> 
    <Grid.RowDefinitions> 
     <RowDefinition Height="25" /> 
     <RowDefinition Height="240*" /> 
     <RowDefinition Height="25" /> 
    </Grid.RowDefinitions> 
    <Grid 
      Name="border1" 
      Grid.Row="1" 
      HorizontalAlignment="Stretch" 
      VerticalAlignment="Stretch" > 
     <ScrollViewer VerticalAlignment="Stretch" 
         Name="scrollViewer1" 
         HorizontalAlignment="Stretch"> 
      <StackPanel 
       Margin="3,3,3,3" 
       Background="Transparent" 
       VerticalAlignment="Stretch" 
       Name="activityStackPanel" 
       HorizontalAlignment="Stretch"> 
      </StackPanel> 
     </ScrollViewer> 
    </Grid> 
    <Button Content="Button" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="12,0,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" /> 
    <Button Content="Button" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="141,0,0,0" Name="button2" VerticalAlignment="Top" Width="75" Click="button2_Click" /> 
    <Label Content="Label" Grid.RowSpan="2" Height="28" HorizontalAlignment="Left" Margin="34,0,0,0" Name="label1" VerticalAlignment="Top" /> 
</Grid> 

namespace WpfApplication10 
{ 
    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 
     } 

     private void button1_Click(object sender, RoutedEventArgs e) 
     { 
      int N = 100; 
      //var r = new ActivityStatisticItem("111", "222", DateTime.Now, "333", 1); 
      for (int i = 0; i < N; i++) 
      { 
       activityStackPanel.Children.Add(new UserControl1()); 
      } 

      label1.Content = activityStackPanel.Children.Count; 
     } 

     private void button2_Click(object sender, RoutedEventArgs e) 
     { 
      activityStackPanel.Children.Clear(); 

      label1.Content = activityStackPanel.Children.Count; 

      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
      GC.Collect(); 
     } 
    } 
} 
<UserControl x:Class="WpfApplication10.UserControl1" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     Background="Transparent" 
     Margin="0,2,0,2" 
     MinHeight="80" 
     MinWidth="130" 
     MaxHeight="80"> 
<Grid Width="441"> 
    <Grid.RowDefinitions> 
     <RowDefinition Height="40" Name="rowTop" /> 
     <RowDefinition Height="40" Name="rowBottom"/> 
    </Grid.RowDefinitions> 
    <Border BorderBrush="Gray" 
      BorderThickness="1" 
      HorizontalAlignment="Stretch" 
      Background="LightGreen" 
      Name="contactPanel" 
      CornerRadius="3,3,3,3" 
      VerticalAlignment="Stretch" Panel.ZIndex="1" > 
     <Grid 
      VerticalAlignment="Stretch" 
      Name="grid1" 
      Margin="3,0,3,0" 
      HorizontalAlignment="Stretch"> 

      <Label Content="Contact" Height="15" HorizontalAlignment="Left" Margin="15,3,0,0" Name="headerLabel" Padding="0" VerticalAlignment="Top" FontSize="10" FontWeight="DemiBold"/> 
      <Label Content="00/00/0000 00:00:00" Height="15" HorizontalAlignment="Left" Margin="13,18,0,0" Name="timeLabel" Padding="0" VerticalAlignment="Top" FontSize="10" Width="100" FontWeight="DemiBold" /> 
      <Label Content="00:00:00" Height="15" HorizontalAlignment="Right" Margin="0,18,0,0" Name="durationLabel" Padding="0" VerticalAlignment="Top" FontSize="10" Width="38" FontWeight="DemiBold"/> 

      <!--<Image Height="12" HorizontalAlignment="Left" Margin="0,3,0,0" Name="directionPictureBox" Stretch="Fill" VerticalAlignment="Top" Width="12" /> 
      <Image Height="12" HorizontalAlignment="Right" Margin="0,20,41,0" Name="timerImage" Stretch="Fill" VerticalAlignment="Top" Width="12"   /> 
      <Image Height="12" HorizontalAlignment="Left" Margin="0,20,0,0" Name="dateTimeImage" Stretch="Fill" VerticalAlignment="Top" Width="12"  />--> 


     </Grid> 
    </Border> 
    <Border BorderBrush="Gray" 
      BorderThickness="1,0,1,1" 
      Grid.Row="1" 
      Background="White" 
      HorizontalAlignment="Stretch" 
      Margin="10,0,10,0" 
      Name="detailsPanel" 
      CornerRadius="0,0,3,3" 
      VerticalAlignment="Stretch"> 
     <Grid HorizontalAlignment="Stretch" 
       Name="grid2" 
       Margin="3,0,3,0" 
       VerticalAlignment="Stretch"> 
      <Label Content="Label" Height="15" HorizontalAlignment="Stretch" FontSize="9" Padding="0" Margin="0,3,0,0" Name="numberRadLabel" VerticalAlignment="Top" /> 
      <Label Content="Label" Height="15" HorizontalAlignment="Stretch" FontSize="9" Padding="0" Margin="0,18,0,0" Name="queueRadLabel" VerticalAlignment="Top" /> 

     </Grid> 
    </Border> 
</Grid> 

En el control del usuario sólo tengo

  public UserControl1() 
     { 
      InitializeComponent(); 
     } 
+1

Nos muestran un cierto código, ¿vale ?! – Aliostad

+0

¿Cómo se mide el "uso de memoria"? En el Administrador de tareas de Windows? – spender

+1

Lo que quiere decir @Alistad es: ¿Puedes proporcionar un código de un * ejemplo de trabajo * mínimo que reproduzca el problema? – Heinzi

Respuesta

11

Quiero liberar la memoria inmediatamente.

Do not. Confíe en GC.

GC.Collect(); 
GC.WaitForPendingFinalizers(); 
GC.Collect(); 

no hacer . Confíe en GC.

Después de 5 - 10 min memory libera

no ¿He dicho GC confianza?


  • modelo de recogida de basura se asegurará de que los no deseados lograron memoria en su sistema está liberado (que incluye casi toda la memoria controles). Utiliza un algoritmo para la optimización que incluye generaciones, memoria libre disponible, posiblemente CPU disponible ... por lo que GC.Collect() interferirá con él.

  • GC.Collect() es asincrónico por lo que no tiene efecto inmediato.

  • El único recurso que debe tener cuidado es el recurso no administrado que generalmente se maneja mediante Dispose Pattern. De lo contrario, no te metas con GC, hace su trabajo muy bien.

+0

Pero no usé recursos no administrados (si no confundo los recursos no administrados son manejadores de recursos nativos, descriptores de archivos, etc.). Si GC no ayudó con lo que puedo usar. En nuestra aplicación, estos controles se agregarán y eliminarán muchas veces, y si esto es una pérdida de memoria, tengo un problema, después de un tiempo la aplicación tomará demasiada memoria y se cayó ( –

+0

Sí, es por eso que no tiene que preocuparse por eso Acabo de mencionarlo por completo. – Aliostad

+0

"No te preocupes por eso", aunque suele ser cierto en los entornos recogidos de basura, se siente un poco ondulado de la mano aquí. Si tienes una ventana que está goteando (y hay muchas maneras en que puede suceda, por ejemplo, no cancelar el registro de un evento suscrito), el método que andron publicado es una forma válida, aunque hacky, de detectar eso. Mi suposición es que el código que publicó era puramente de prueba. – DavidN

0

Estoy de acuerdo con @Aliostad re GC. Hice su trabajo muy bien, PERO no es una herramienta para limpiar mágicamente toda su memoria.

Si tiene problemas de pérdida de memoria, la solución más directa y confiable es utilizar un generador de perfiles, que debería poder identificar si tiene una fuga genuina y dónde está. He usado hormigas de Red Gate, pero otras pueden tener mejores sugerencias.

Además de seguir las pautas habituales, como asegurarse de desechar correctamente las cosas. Llamar a GC y esperar que funcione no es una alternativa para una correcta evaluación del código.

1

En un entorno de recolección de basura, liberar memoria inmediatamente no tiene sentido.

Dado que los CLR JITs codifican a pedido, la primera vez que ejecute la prueba no debería ver que la memoria vuelva a bajar a su estado inicial. Esto tiene sentido porque se han seguido nuevas rutas de código y el código ha sido JITted. Ese código necesita residir en algún lugar de la memoria no?

Por lo tanto, después de su primera ejecución de prueba, no podrá volver a recopilar su huella de memoria inicial. Su línea base debe ser el uso de memoria que obtiene después de ejecutar la prueba una vez, no antes. Después de ejecutarlo por segunda vez, puedo recuperar la memoria a la línea de base con varias colecciones.

Además, recomendaría ejecutar su proyecto en modo de lanzamiento sin un depurador adjunto. Ejecutar su programa con un depurador conectado no le mostrará un verdadero perfil de memoria, ya que hay varios trucos que emplea para mantener los objetos alrededor (por ejemplo, Collect objects still in scope - GC.Collect).

Esto es todo un punto discutible, sin embargo, porque como dije antes, reclamar memoria inmediatamente no tiene mucho sentido en un entorno GC (en la mayoría de los casos).

4
GC.Collect(); 
GC.WaitForPendingFinalizers(); 
GC.Collect(); 

Esta es una manera segura de forzar objetos no GCable en Gen2 antes de tiempo, lo que aumenta su consumo de memoria por un período de tiempo más largo, sin una buena razón.

Como dijo Aliostad: ¡no!

3

Deje el recolector de basura solo y deje que haga su trabajo.

Lo que está describiendo no es una pérdida de memoria. Su memoria dinámica no se libera en el momento en que crees que debería ser lanzada.

¿Eres el recolector de basura? Tu no eres. Preocuparse por la recolección de basura no es su trabajo. Si estos objetos son realmente basura, y lo son, la memoria estará allí cuando la necesites.

+0

"¿Eres el recolector de basura? Tú no eres ... "Me encanta esto y lo usaré: D – Andy

0

Mediante el uso de esta DLL de invocación podemos realocate los recursos de memoria

public class MemoryManagement 
{ 
[DllImportAttribute("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet = 
CharSet.Ansi, SetLastError = true)] 

private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int 
maximumWorkingSetSize); 

public static void FlushMemory() 
{ 
GC.Collect(); 
GC.WaitForPendingFinalizers(); 
if (Environment.OSVersion.Platform == PlatformID.Win32NT) { SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); 
} 
}