2012-05-17 19 views
9

Do tej pory moje testy wykazały, że wszystkie standardowe podejścia, przykłady i ramy wykorzystujące wzorzec MVVM w silverlight cierpią z powodu ogromnego problemu: ogromnych wycieków pamięci, które uniemożliwiają gromadzenie się maszyn wirtualnych.Silverlight + MVVM + Wiązania = przecieki pamięci?

Oczywiście jest to ogromna i śmieszne twierdzenie - więc moje oczekiwanie, że ktoś będzie miał oczywistą odpowiedź, dlaczego i dokąd idę źle :)

Kroki do odtworzenia są proste:

  • powiązać viewmodel do widoku przez ustawienie DataContext widoki na VM (zakładamy ViewModel wykorzystuje INotifyPropertyChanged wspierać powiązania danych)
  • oprawę elementu UI do nieruchomości na viewmodel, na przykład:

<TextBox Text="{Binding SomeText}" />

  • Dźwignia wiązanie w jakiś sposób (na przykład - wystarczy wpisać w polu tekstowym).

To tworzy łańcuch referencyjny, który rozciąga się od korzenia, do BindingExpression, do twojego viewmodelu. Następnie możesz usunąć widok z drzewa interfejsu użytkownika, a także wszystkie odwołania do maszyny wirtualnej - jednak maszyna wirtualna nigdy nie zostanie usunięta ze śmiechu, dzięki korzeniu referencji <> BindingExpression <> VM.

Stworzyłem dwa przykłady ilustrujące problem. Mają przycisk do utworzenia nowego widoku/viewmodel (który powinien zrzucić wszystkie odniesienia do starych) i przycisk, który wymusza odśmiecanie i raporty dotyczące bieżącego użycia pamięci.

Przykład 1 jest superpłaskiętym mikro przykładem mikroobróbki. Przykład 2 nie używa żadnych frameworków i po prostu ilustruje problem w najprostszy sposób, jaki mogłem wymyślić.

Example 1

Example 2

Dla tych, którzy mogą ci pomóc, ale nie chcą, aby pobrać przykładowe projekty, oto kod dla przykładu 2. Zaczynamy viewmodel zwanego FooViewModel:

public class FooViewModel : INotifyPropertyChanged 
{ 
    string _fooText; 

    public string FooText 
    { 
     get { return _fooText; } 
     set 
     { 
      _fooText = value; 
      NotifyPropertyChanged("FooText"); 
     } 
    } 

    private byte[] _data; 
    public FooViewModel() 
    { 
     _data = new byte[10485760]; //use up 10mb of memory 
    } 



    private void NotifyPropertyChanged(String info) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(info)); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

Po prostu eksponuje właściwość łańcucha o nazwie FooText, która również zostanie powiązana. INotifyPropertyChanged jest niezbędny do ułatwienia wiązania.

Następnie mamy widok nazwie FooView który jest usercontrol zawierające:

<UserControl x:Class="MVVMLeak.FooView"> 
    <StackPanel x:Name="LayoutRoot" Orientation="Horizontal"> 
     <TextBlock Text="Bound textbox: " /> 
     <TextBox Text="{Binding FooText}" Width="100"/> 
    </StackPanel> 
</UserControl> 

(nazw pominięte dla zwięzłości)

Ważną nieco Oto tekstowe, które jest związane z właściwością FooText.Oczywiście musimy ustawić datacontext, które zdecydowałem się zrobić w kodzie zamiast wprowadzać ViewModelLocator:

public partial class FooView : UserControl 
{ 
    public FooView() 
    { 
     InitializeComponent(); 
     this.DataContext = new FooViewModel(); 
    } 
} 

tytułowa wygląda tak:

<StackPanel x:Name="LayoutRoot" Background="White"> 

    <Button Click="Button_Click" Content="Click for new FooView"/> 
    <Button Click="Button2_Click" Content="Click to garbage collect"/> 
    <ContentControl x:Name="myContent"></ContentControl> 
</StackPanel> 

z następujących czynności w kodzie za:

private void Button_Click(object sender, RoutedEventArgs e) 
    { 
     myContent.Content = new FooView(); 
    } 

    private void Button2_Click(object sender, RoutedEventArgs e) 
    { 
     MessageBox.Show("Memory in use after collection: " + (GC.GetTotalMemory(true)/1024/1024).ToString() + "MB"); 
    } 

Uwaga: aby odtworzyć problem, należy wpisać coś w polu tekstowym, jak wierzę, wiązanie ekspresja nie jest tworzony, dopóki jest to potrzebne.

Warto zauważyć, że this KB article mogą być związane, jednak nie jestem przekonany, że skoro „Metoda 2” obejście nie wydaje się mieć wpływ, a łańcuch odniesienia wydaje się nie pasować.

Ponadto, nie jestem pewien, czy to ma znaczenie, ale użyłem CLR Profiler do zdiagnozowania przyczyny.

Aktualizacja:

Jeśli ktoś chciałby przetestować i zgłosić swoje wnioski w komentarzu, jestem gospodarzem aplikacji silverlight poprzez Dropbox tutaj: Hosted Example. Aby odtworzyć: uderz w górny botton, wpisz coś, naciśnij górny przycisk, wpisz coś, naciśnij górny przycisk. Następnie naciśnij przycisk. Jeśli zgłasza 10MB użytkowania (lub może jakiejś innej kwoty, która nie rośnie), nie występują wycieki pamięci.

Do tej pory problem zdaje się występować na WSZYSTKICH naszych maszynach rozwojowych, którymi są ThinkPad w510 (43192RU) z 12 GB pamięci RAM, 64-bitowym Winem 7 Enterprise. Obejmuje to niektóre, które nie miały zainstalowanych narzędzi programistycznych. Warto zauważyć, że działają na stacji roboczej VMWare.

Problem nie wydaje się występować na innych urządzeniach, które wypróbowałem - w tym kilka komputerów domowych i innych komputerów w biurze. W pewnym stopniu wykluczyliśmy wersje SL, ilość pamięci i prawdopodobnie vmware. Wciąż nie przyłożyłem przyczyny.

+0

niepożądane: naprawdę brzydkie/brzydkie obejście dla caliburn micro (i mojego obecnego podejścia) polega na ustawieniu wszystkich zmiennych prywatnych na wartość null w trybie OverDide OnDeactivate (true). Przynajmniej wtedy przeciekijemy wolniej, ale to w najlepszym wypadku. –

+0

Jeśli wyciek występuje tylko podczas pracy, należy spojrzeć na to: http://stackoverflow.com/questions/8544458/memory-leak-in-mvvm-design-pattern-suggested-by-josh-smith- at-msdn –

+0

Nie obejrzałam tej próbki, ale przypuszczam, że to ten sam problem. –

Odpowiedz

2

Rozwiązanie jest jeszcze można znaleźć jednak problem jest obecnie identyfikowane .Takie zachowanie będzie występować, jeśli Silverlights' wydziały automatyki są wywoływane z powodu:

  • Tablet PC Służby wejściowa (innymi słowy, wszystko «tablet jak» PC) narzędzia
  • automatycznego testowania
  • czytniki ekranu (i inne oprogramowanie accessability)

Więcej informacji tutaj: http://www.wintellect.com/cs/blogs/sloscialo/archive/2011/04/13/silverlight-memory-leaks-and-automationpeers.aspx

więc nowe powierzchnie Problem: W jaki sposób możemy wyłączyć automationpeers lub w inny sposób uzyskać czy prawidłowo je oczyścić?

Ten post przedstawia jedno podejście: WPF UserControl Memory leak

Jednak tak naprawdę nie jest dobrym rozwiązaniem jako musielibyśmy zastąpić każdą silverlight kontrolę które planujemy wykorzystać do wiązania, nie wspominając o szablony kontrolnych złożone kontrole.

zmienię moją odpowiedź, jeśli ktoś może zidentyfikować dobre rozwiązanie, ale na razie nie wydaje się być jednym ...

Edit:

Oto miły mały obejście, które wydaje się wykonywać tę pracę. Wystarczy dodać następujący parametr w kodzie HTML, gdzie można zdefiniować obiekt silverlight:

<param name="windowless" value="true" /> 

Skutkiem ubocznym prowadzenia w trybie „okien” jest to, że automatyzacja nie działa :) notatka

+0

Uważam, że nie ma takiego obejścia dla aplikacji poza przeglądarką? – John

+0

A tak przy okazji, nie mogę ci wystarczająco podziękować za to, że to zbadałeś. Najwyraźniej nikogo to nie obchodzi. – John

0

W twoim drugim przykładzie nie ma wycieku pamięci.

Po wpłynąć nowy FooView instancji do swojej ContentControl użyciu myContent.Content = new FooView(); istnieje nie więcej używany do całego View + ViewModel obiektu wykresu.

W razie potrzeby zostaną zebrane w razie potrzeby.

Może Pan wyjaśnić, co sprawi, że myślę, że jest wyciek pamięci (tj statystyka, repro kroki ... itd.)

+0

Jak wspomniano w aktualizacji, wyciekiem wydaje się być komputer i/lub środowisko specyficzne dla niektórych komputerów (prawdopodobnie niewielka część komputerów). Zgadzam się, że * nie powinno * być przeciekiem pamięci, ale zdecydowanie taki jest. Ponieważ problem pojawia się tylko na naszym laptopie w510 szt., Zgłosiłem problem na razie, ponieważ zabrakło mi pomysłów. –

Powiązane problemy