Laden...

Speicherüberlauf bei häufiger Aktualisierung der ListBox

Erstellt von storck vor 13 Jahren Letzter Beitrag vor 13 Jahren 3.977 Views
S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren
Speicherüberlauf bei häufiger Aktualisierung der ListBox

Bin in meinem Projekt inzwischen sehr weit fortgeschritten und hab jetzt ein sehr großes Problem:

auf einer Seite verwende ich eine ListBox, in der ich analoge Sensorwerte anzeige. Da es sich eben um Sensorwerte handelt, "flackern" diese eben und dadurch kommt es zu häufigen Aktualisierungen. Die ListBox wird mittels Databinding von einer ObservableCollection gefüttert. Die Objekte der OC erben von INotifyPropertyChanged, damit Änderungen automatisch in der ListBox aktualisiert werden.

Wenn ich die Aktualisierungen "abschalte" ist alles in Ordnung, wenn diese aber aktiviert sind füllt sich der Speicher der Anwendung kontinuierlich, bis sie durch eine OutOfMemoryException abstürzt (wird als unhandled exception gefangen und das programm geregelt beendet)

hab schon google und die SuFu durchforstet, finde aber keinen entscheidenden hinweis. auch in den microsoft blogs wird berichtet, dass man es so machen soll wie ich es mache (mit observable collection und inotifypropertychanged).

kennt jemand dieses problem beziehungsweise hat es jemand in den griff bekommen?

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

wird die ListBox nur mit Werten gefüllt oder werden auch alte Werte gelöscht?
Kannst du ermitteln bei wievielen Elementen in der ListBox die Exception kommt?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren

Also die ListBox wird einmal mit 33 Elemente gefüllt, danach werden die Werte nur noch aktualisiert, die Anzahl der Elemente bleibt gleich.

beste grüße uwe

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

Wenn ich die Aktualisierungen "abschalte" ist alles in Ordnung

Was meinst du damit genau?

Ich vermute die Ursache für den Fehler darin dass irgendein IDisposable-Objekt nicht freigegeben wird und zwar in deinem Code 😉 zB beim Einlesen der Sensorwerte.

Hast du die Möglichkeit mit einem Profiler zu gucken? Oder zeig mal den Code wie du einliest.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.044 Beiträge seit 2008
vor 13 Jahren

Hallo regarding,

eine Ferndiagnose zu stellen ist sehr schwierig. Ich kann dir auf dem Stehgreif nicht sagen, wo das Problem ist. Ich vermute mal, dass im Speicher irgendwas voll läuft, sodass es zu dieser Exception kommt. Am besten ist es, wenn du mal mit einem Profiler mal schaust, wo das Problem ist. Es wäre auch möglich, dass WPF nicht dran schuld ist, sondern die Funktion, mit der auf die Sensorwerte zugegriffen wird.

zero_x

S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren

Ich wollte damit nur darauf hinaus, dass der Hund in der Aktualisierung der ListBox begraben sein muss.

Das Einlesen der Sensorwerte erfolgt eventbasiert. Bei Wertänderung auf der Steuerung (B&R) erhalte ich ein Event. Über eine Update-Methode aktualisiere ich dann den Wert in meiner Collection

public class AnalogIOElementsCollection : ObservableCollection<AnalogIOElement>

Die Klasse AnalogIOElement erbt von INotifyPropertyChanged und das löst automatisch die Aktualisierung aus.

S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren

Also dass es am Zugriff auf die Sensorwerte liegt mag ich bezweifeln, denn ich mache das immer gleich und auf einer anderen Seite wo ich auch Sensorwerte binde (an progressbars und andere eigene controls) füllt sich der Speicher nicht, obwohl ich auch hier ständige Änderungen habe.

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

warum kannst du das so genau eingrenzen?

Hast du die Möglichkeit mit einem Profiler zu gucken? Oder zeig mal den Code wie du einliest.

Sonst können wir nur raten. Bitte beachte auch [Hinweis] Wie poste ich richtig? Punkt 5

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren

warum kannst du das so genau eingrenzen?

sorry, wie kann ich was so genau eingrenzen?

das ist der xaml-code mit der listbox:


<UserControl.Resources>

        <converter:TrueFalseConverter x:Key="trueFalseConverter" />
        <converter:RawValueConverter x:Key="rawValueConverter" />
        <converter:PhysicalValueConverter x:Key="physicalValueConverter" />

        <CollectionViewSource x:Key="cvs" Source="{Binding}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName="Group" />
            </CollectionViewSource.SortDescriptions>
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="Group" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>

        <Style x:Key="ItemContainer" TargetType="{x:Type ListBoxItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <Border Background="Black"
                                BorderThickness="0"
                                Width="1000"
                                Margin="0,0,0,0"
                                VerticalAlignment="Top"
                                x:Name="ItemBorder">
                            <ContentPresenter />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style x:Key="GroupContainer" TargetType="{x:Type GroupItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Expander Header="{Binding Name}" 
                                  IsExpanded="True" 
                                  Foreground="White" 
                                  FontSize="14"
                                  FontWeight="Bold"
                                  Width="1000"
                                  x:Name="GroupExpander"
                                  Background="{StaticResource SandvikBlue}">
                            <ItemsPresenter />
                        </Expander>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>

    <StackPanel Orientation="Vertical">
        <Border CornerRadius="7" BorderBrush="{StaticResource SandvikBlue}" BorderThickness="3" Width="1000" Height="40" Margin="10,0,0,0" HorizontalAlignment="Center" Background="LightGray">
            <Label Name="analogInputsHeader" Foreground="{StaticResource SandvikBlue}" FontWeight="Bold" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="0"></Label>
        </Border>

        <Grid Width="1024" Height="560">
            <Grid.RowDefinitions>
                <RowDefinition Height="0" />
                <RowDefinition />
            </Grid.RowDefinitions>

            <ListBox Grid.Row="1" 
                     Name="IOListBox" 
                     HorizontalAlignment="Center" 
                     Margin="10,0,0,0" 
                     IsSynchronizedWithCurrentItem="True" 
                     ItemContainerStyle="{StaticResource ItemContainer}"
                     ItemsSource="{Binding Source={StaticResource cvs}}" 
                     Background="Black" 
                     SelectionMode="Single" 
                     BorderThickness="0" 
                     Height="560" 
                     ScrollViewer.HorizontalScrollBarVisibility="Hidden">
                <ListBox.GroupStyle>
                    <GroupStyle ContainerStyle="{StaticResource GroupContainer}" />
                </ListBox.GroupStyle>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Border>
                            <StackPanel Orientation="Horizontal">
                                <Rectangle Width="25" Height="13" Fill="{Binding Path=State, Converter={StaticResource trueFalseConverter}}" />
                                <Label Width="150" Content="{Binding Path=LayoutReference}" Foreground="White" FontWeight="Normal" Margin="15,0,0,0" />
                                <Label Width="515" Content="{Binding Path=DE_Description}" Foreground="White" FontWeight="Normal" />
                                <Label Width="150" Foreground="White" FontWeight="Normal" HorizontalContentAlignment="Center">
                                    <MultiBinding Converter="{StaticResource rawValueConverter}">
                                        <Binding Path="RawValue" />
                                        <Binding Path="RawUnit" />
                                        <Binding Path="RawScaleFactor" />
                                        <Binding Path="RawOffset" />
                                    </MultiBinding>
                                </Label>
                                <Label Width="150" Foreground="White" FontWeight="Normal" HorizontalContentAlignment="Center">
                                    <MultiBinding Converter="{StaticResource physicalValueConverter}">
                                        <Binding Path="PhysicalValue" />
                                        <Binding Path="PhysicalOffset" />
                                        <Binding Path="PhysicalScaleFactor" />
                                        <Binding Path="PhysicalUnit" />
                                    </MultiBinding>
                                </Label>
                            </StackPanel>
                        </Border>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </StackPanel>

Wenn das Event der Wertänderung kommt ermittle ich das betreffende Element und aktualisiere so:

AnalogIOPageController.GetInstance.AnalogIOElementsCollection.UpdateRawValue(index, variableField.Value[changedItem]);

variableField.Value[changedItem] ist der neue Wert des geänderten elements.

ich verwende das singleton-pattern, damit die daten auch sicher nur in einem objekt gespeichert sind. AnalogIOElementsCollection ist die ObservableCollection die als Datenquelle fürs Binding verwendet wird.

Die update-funktion sieht folgendermaßen aus:

public void UpdateRawValue(int id, int rawValue)
        {
            foreach (AnalogIOElement item in this)
            {
                if (item.ID == id)
                {
                    item.RawValue = rawValue;
                    break;
                }
            }
        }

Einen profiler hab ich leider nicht in verwendung.

Rein funktional betrachtet funktioniert ja alles, die Werte ändern sich in der ListBox. Nur leider steigt der Speicherbedarf der Anwendung kontinuierlich an wenn ich auf dieser einen Seite bin. Der manuelle Aufruf des GC verlangsamt das Ganze zwar, verhindern kann er es aber nicht.

6.862 Beiträge seit 2003
vor 13 Jahren

Hallo,

wie sehen denn deine Converter aus?

Baka wa shinanakya naoranai.

Mein XING Profil.

S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren
public class PhysicalValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int physValue = (int)values[0];
            int offset = (int)values[1];
            double scale = (double)values[2];
            string unit = (string)values[3];

            double value = (double)(physValue + offset) * scale;

            return String.Format("{0}{1}", value, unit);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
public class RawValueConverter : IMultiValueConverter
    {
        #region IMultiValueConverter Members

        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int value = (int)values[0];
            string unit = (string)values[1];
            double scale = (double)values[2];
            int offset = (int)values[3];

            double val = (value + offset) * scale;

            if (unit == "0,1°C")
            {
                return String.Format("{0:0.00} [0,1°C]", val);
            }
            else if (unit == "mA")
            {
                return String.Format("{0:0.00} [mA]", val);
            }
            else
            {
                return String.Format("{0:0.00}", val);
            }
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
[ValueConversion(typeof(bool), typeof(SolidColorBrush))]
    public class TrueFalseConverter : IValueConverter
    {
        #region IValueConverter Members

        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value produced by the binding source.</param>
        /// <param name="targetType">The type of the binding target property.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {            
            if ((bool)value == true)
            {
                return new SolidColorBrush(Colors.Red);
            }
            else
            {
                return new SolidColorBrush(Colors.Green);
            }
        }

        /// <summary>
        /// Converts a value.
        /// </summary>
        /// <param name="value">The value that is produced by the binding target.</param>
        /// <param name="targetType">The type to convert to.</param>
        /// <param name="parameter">The converter parameter to use.</param>
        /// <param name="culture">The culture to use in the converter.</param>
        /// <returns>
        /// A converted value. If the method returns null, the valid null value is used.
        /// </returns>
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

Die sind eigentlich alle immer nach dem selben Prinzip gestrickt...

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

new SolidColorBrush ist das Problem. Ist IDisposable (nur in GDI+ und nicht in WPF) und wird immer neu erzeugt und nie freigegeben. Erstell die Brushes nur 1x und verwende diese.

Oder du legst sie in XAML in eine Resource und änderst die Darstellung per Trigger - dann kannst du dir auch diesen Converter sparen. Der Zugriff dann via StaticResource.

mfG Gü

Edit: Korrektur gem. Jacks Beitrag.

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Gelöschter Account
vor 13 Jahren

return new SolidColorBrush(Colors.Red);

-->

 return Brushes.Red;

Einen profiler hab ich leider nicht in verwendung.

Dann hol dir einen... sonst ist das ganze mit Kaffesatzlesen gleichzusetzen!

edit:

new SolidColorBrush ist das Problem. Ist IDisposable

In GDI+ ja aber nicht in WPF soweit ich das sehe.

S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren

hab die new SolidColorBrush jetzt gegen return Brushes ausgetauscht. Ob es die gewünscht Abhilfe schafft kommt am Nachmittag auf. Grundsätzlich wäre es durchaus möglich, denn ich habe auch schon gelesen, dass man Bilder und eben auch SolidColorBrush usw freezen soll wenn sie unverändert bleiben, damit sie nicht ständig auf events hören und die performance belasten.

was mich aber noch etwas irritiert: ich dachte bei wpf wird nur das neu gezeichnet, was sich ändert. sprich wenn sich ein Wert in der ListBox ändert wird nicht alles neu gezeichnet? Oder bin ich damit auf dem Holzweg. Weil dann bringt das mit der Farbe sicher nichts, denn die ändert sich so gut wie nie.

Installiere mir gerade das Performance Profiling Toolkit von MS, mal sehen was das kann bzw. ob ich damit was anzufangen weiß.

danke schon mal für eure hilfe!!!

S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren

Ich hab jetzt einiges ausprobiert und bin zu dem Schluss gekommen, es muss an der Anzeige liegen! Folgendes hab ich probiert:

  • die Datenquelle weiterhin aktualisiert, aber nicht an die View gebunden -> Speicher bleibt ok. D.h. es liegt nicht an der behandlung der daten bzw. am zugriff darauf
  • die werte ohne konverter ausgegeben -> Speicher wird voll
  • aus solidcolorbrush brushes gemacht -> speicher wird voll

und im profiler sieht man auch, dass ständig die gesamte listbox neu gezeichnet wird.

1.044 Beiträge seit 2008
vor 13 Jahren

Hallo storck,

und im profiler sieht man auch, dass ständig die gesamte listbox neu gezeichnet wird.

Das kann dann wohl nur das Binding sein. Schau mal, ob das mit Testdaten genau so ist. Es wäre auch möglich, dass irgendwo der Speicher nicht freigegeben wird. Über das Thema habe ich gebloggt. 😉

zero_x

S
storck Themenstarter:in
45 Beiträge seit 2009
vor 13 Jahren

Hab heute schon mein Glück mit einem DataGrid versucht, aber auch hier dieselbe Problematik.

Jetzt hab ichs mit einem STackpanel versucht in dem sich mehrere Labels befinden, das ich zur laufzeit dynamisch erstelle und da funktioniert es! Der Speicher bleibt konstant! Es tut dem Entwicklerherz zwar weh, aber die Performance der ListBox und des DataGrids sind leider nicht tragbar.