Freigeben über


Verbessern der App-Leistung

Schlechte App-Leistung stellt sich auf viele Arten dar. Es kann dazu führen, dass eine App nicht reagiert, das Scrollen verlangsamt und die Akkulaufzeit des Geräts verringert. Die Optimierung der Leistung erfordert jedoch mehr als nur die Implementierung von effizientem Code. Die Benutzererfahrung in Bezug auf die App-Leistung muss ebenfalls berücksichtigt werden. So kann beispielsweise sichergestellt werden, dass Vorgänge ausgeführt werden, ohne den Benutzer daran zu hindern, andere Aktivitäten auszuführen, um die Benutzerfreundlichkeit zu verbessern.

Es gibt viele Techniken zum Erhöhen der Leistung und der wahrgenommenen Leistung von .NET Multi-Platform App UI (.NET MAUI)-Apps. Zusammen können diese Techniken die Menge der Arbeit, die von einer CPU ausgeführt wird, und die Menge des von einer App verbrauchten Arbeitsspeichers erheblich reduzieren.

Verwenden Sie einen Profiler

Beim Entwickeln einer App ist es wichtig, nur zu versuchen, Code zu optimieren, nachdem sie profiliert wurde. Die Profilerstellung ist ein Verfahren zum Bestimmen, wo Codeoptimierungen den größten Einfluss auf die Verringerung von Leistungsproblemen haben. Der Profiler verfolgt die Speicherauslastung der App und zeichnet die Laufzeit der Methoden in der App auf. Diese Daten helfen, durch die Ausführungspfade der App und die Ausführungskosten des Codes zu navigieren, damit die besten Optimierungsmöglichkeiten ermittelt werden können.

.NET MAUI-Apps können mit dotnet-trace unter Android, iOS, Mac, und Windows, und mit PerfView unter Windows profiliert werden. Weitere Informationen finden Sie unter Profiling .NET MAUI-Apps.

Die folgenden bewährten Methoden werden bei der Profilerstellung einer App empfohlen:

  • Vermeiden Sie die Profilerstellung einer App in einem Simulator, da der Simulator die App-Leistung möglicherweise verzerrt.
  • Idealerweise sollten Profilerstellungen auf einer Vielzahl von Geräten durchgeführt werden, da leistungsmessungen auf einem Gerät nicht immer die Leistungsmerkmale anderer Geräte zeigen. Die Profilerstellung sollte jedoch mindestens auf einem Gerät durchgeführt werden, das die niedrigste erwartete Spezifikation aufweist.
  • Schließen Sie alle anderen Apps, um sicherzustellen, dass die Leistung der profilierten App und nicht die der anderen Apps gemessen wird.

Verwenden kompilierter Bindungen

Kompilierte Bindungen verbessern die Datenbindungsleistung in .NET MAUI-Apps, indem Bindungsausdrücke zur Kompilierungszeit und nicht zur Laufzeit mit Spiegelung aufgelöst werden. Durch das Kompilieren eines Bindungsausdrucks wird kompilierter Code generiert, der in der Regel eine 8-20-mal schnellere Bindung als die Verwendung einer klassischen Bindung auflösen kann. Weitere Informationen finden Sie unter Kompilierte Bindungen.

Reduzieren von unnötigen Bindungen

Verwenden Sie keine Bindungen für Inhalte, die problemlos statisch festgelegt werden können. Es gibt keinen Vorteil beim Binden von Daten, die nicht gebunden werden müssen, da Bindungen nicht kosteneffizient sind. Beispielsweise hat das Festlegen von Button.Text = "Accept" weniger Aufwand als das Binden von Button.Text an eine Ansichtsmodell-Eigenschaft string mit dem Wert "Akzeptieren".

Wähle das richtige Layout aus

Ein Layout, das mehrere untergeordnete Elemente anzeigen kann, aber nur ein einziges untergeordnetes Element aufweist, ist verschwenderisch. Das folgende Beispiel zeigt z. B. eine VerticalStackLayout mit einem einzelnen Kind:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage">
    <VerticalStackLayout>
        <Image Source="waterfront.jpg" />
    </VerticalStackLayout>
</ContentPage>

Dies ist verschwendet, und das VerticalStackLayout-Element sollte entfernt werden, wie im folgenden Beispiel gezeigt:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage">
    <Image Source="waterfront.jpg" />
</ContentPage>

Versuchen Sie außerdem nicht, die Darstellung eines bestimmten Layouts mithilfe von Kombinationen anderer Layouts zu reproduzieren, da dies zu unnötigen Layoutberechnungen führt. Versuchen Sie beispielsweise nicht, ein Grid Layout mithilfe einer Kombination aus HorizontalStackLayout Elementen zu reproduzieren. Das folgende Beispiel zeigt ein Beispiel für diese schlechte Vorgehensweise:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage">
    <VerticalStackLayout>
        <HorizontalStackLayout>
            <Label Text="Name:" />
            <Entry Placeholder="Enter your name" />
        </HorizontalStackLayout>
        <HorizontalStackLayout>
            <Label Text="Age:" />
            <Entry Placeholder="Enter your age" />
        </HorizontalStackLayout>
        <HorizontalStackLayout>
            <Label Text="Occupation:" />
            <Entry Placeholder="Enter your occupation" />
        </HorizontalStackLayout>
        <HorizontalStackLayout>
            <Label Text="Address:" />
            <Entry Placeholder="Enter your address" />
        </HorizontalStackLayout>
    </VerticalStackLayout>
</ContentPage>

Dies ist verschwenderisch, da unnötige Layoutberechnungen durchgeführt werden. Stattdessen kann das gewünschte Layout mit einem Gridbesser erreicht werden, wie im folgenden Beispiel gezeigt:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage">
    <Grid ColumnDefinitions="100,*"
          RowDefinitions="30,30,30,30">
        <Label Text="Name:" />
        <Entry Grid.Column="1"
               Placeholder="Enter your name" />
        <Label Grid.Row="1"
               Text="Age:" />
        <Entry Grid.Row="1"
               Grid.Column="1"
               Placeholder="Enter your age" />
        <Label Grid.Row="2"
               Text="Occupation:" />
        <Entry Grid.Row="2"
               Grid.Column="1"
               Placeholder="Enter your occupation" />
        <Label Grid.Row="3"
               Text="Address:" />
        <Entry Grid.Row="3"
               Grid.Column="1"
               Placeholder="Enter your address" />
    </Grid>
</ContentPage>

Optimieren von Bildressourcen

Bilder sind einige der teuersten Ressourcen, die Apps verwenden, und werden häufig mit hohen Auflösungen erfasst. Während dies lebendige Bilder voller Details erzeugt, benötigen Apps, die solche Bilder anzeigen, in der Regel mehr CPU-Auslastung, um das Bild zu decodieren und mehr Arbeitsspeicher zu speichern. Es ist verschwenderisch, ein hochauflösendes Bild im Speicher zu dekodieren, wenn es für die Anzeige auf eine kleinere Größe skaliert wird. Verringern Sie stattdessen die CPU-Auslastung und den Speicherbedarf, indem Sie Versionen gespeicherter Bilder erstellen, die nahe an den vorhergesagten Anzeigegrößen liegen. Beispielsweise sollte ein bild, das in einer Listenansicht angezeigt wird, höchstwahrscheinlich eine niedrigere Auflösung haben als ein Bild, das im Vollbildmodus angezeigt wird.

Darüber hinaus sollten Bilder nur bei Bedarf erstellt und veröffentlicht werden, sobald sie von der App nicht mehr benötigt werden. Wenn eine App z. B. ein Bild anzeigt, indem sie seine Daten aus einem Datenstrom liest, stellen Sie sicher, dass der Datenstrom nur erstellt wird, wenn er erforderlich ist, und stellen Sie sicher, dass der Datenstrom freigegeben wird, wenn er nicht mehr benötigt wird. Dies kann erreicht werden, indem der Datenstrom erstellt wird, wenn die Seite erstellt wird oder wenn das Page.Appearing-Ereignis ausgelöst wird, und indem der Datenstrom entsorgt wird, wenn das Page.Disappearing-Ereignis ausgelöst wird.

Stellen Sie beim Herunterladen eines Bilds für die Anzeige mit der ImageSource.FromUri(Uri)-Methode sicher, dass das heruntergeladene Bild für einen geeigneten Zeitraum zwischengespeichert wird. Weitere Informationen finden Sie unter Zwischenspeichern von Bildern.

Verringern der Anzahl von Elementen auf einer Seite

Wenn Sie die Anzahl der Elemente auf einer Seite verringern, wird die Seite schneller gerendert. Es gibt zwei Haupttechniken, um dies zu erreichen. Die erste besteht darin, Elemente auszublenden, die nicht sichtbar sind. Die IsVisible-Eigenschaft jedes Elements bestimmt, ob das Element auf dem Bildschirm sichtbar sein soll. Wenn ein Element nicht sichtbar ist, da es hinter anderen Elementen verborgen ist, entfernen Sie entweder das Element, oder legen Sie seine IsVisible-Eigenschaft auf falsefest. Wenn Sie die IsVisible-Eigenschaft für ein Element auf false festlegen, bleibt das Element im visuellen Baum erhalten, schließt es jedoch von Rendering- und Layoutberechnungen aus.

Die zweite Technik besteht darin, unnötige Elemente zu entfernen. Das folgende Beispiel zeigt ein Seitenlayout mit mehreren Label Elementen:

<VerticalStackLayout>
    <VerticalStackLayout Padding="20,20,0,0">
        <Label Text="Hello" />
    </VerticalStackLayout>
    <VerticalStackLayout Padding="20,20,0,0">
        <Label Text="Welcome to the App!" />
    </VerticalStackLayout>
    <VerticalStackLayout Padding="20,20,0,0">
        <Label Text="Downloading Data..." />
    </VerticalStackLayout>
</VerticalStackLayout>

Dasselbe Seitenlayout kann mit einer reduzierten Elementanzahl beibehalten werden, wie im folgenden Beispiel gezeigt:

<VerticalStackLayout Padding="20,35,20,20"
                     Spacing="25">
    <Label Text="Hello" />
    <Label Text="Welcome to the App!" />
    <Label Text="Downloading Data..." />
</VerticalStackLayout>

Verringern der Größe des Anwendungsressourcenwörterbuchs

Alle Ressourcen, die in der gesamten App verwendet werden, sollten im Ressourcenverzeichnis der App gespeichert werden, um Duplizierungen zu vermeiden. Dadurch wird die Menge an XAML reduziert, die in der gesamten App analysiert werden muss. Das folgende Beispiel zeigt die HeadingLabelStyle Ressource, die appweit verwendet wird, und ist daher im Ressourcenverzeichnis der App definiert:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.App">
     <Application.Resources>
        <Style x:Key="HeadingLabelStyle"
               TargetType="Label">
            <Setter Property="HorizontalOptions"
                    Value="Center" />
            <Setter Property="FontSize"
                    Value="Large" />
            <Setter Property="TextColor"
                    Value="Red" />
        </Style>
     </Application.Resources>
</Application>

XAML-Code, der für eine Seite spezifisch ist, sollte jedoch nicht im Ressourcenverzeichnis der App enthalten sein, da die Ressourcen dann beim App-Start analysiert werden, statt wenn eine Seite erforderlich ist. Wenn eine Ressource von einer Seite verwendet wird, die nicht die Startseite ist, sollte sie im Ressourcenwörterbuch dieser Seite platziert werden, um den XAML-Code zu reduzieren, der beim Starten der App analysiert wird. Das folgende Beispiel zeigt die HeadingLabelStyle Ressource, die sich nur auf einer einzelnen Seite befindet, und ist daher im Ressourcenverzeichnis der Seite definiert:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyMauiApp.MainPage">
    <ContentPage.Resources>
        <Style x:Key="HeadingLabelStyle"
                TargetType="Label">
            <Setter Property="HorizontalOptions"
                    Value="Center" />
            <Setter Property="FontSize"
                    Value="Large" />
            <Setter Property="TextColor"
                    Value="Red" />
        </Style>
    </ContentPage.Resources>
    ...
</ContentPage>

Weitere Informationen zu App-Ressourcen finden Sie unter Apps im Stil von XAML.

Verringern der Größe der App

Wenn .NET MAUI Ihre App buildet, kann ein Linker ILLink verwendet werden, um die Gesamtgröße der App zu verringern. ILLink reduziert die Größe, indem der vom Compiler erstellte Zwischencode analysiert wird. Sie entfernt nicht verwendete Methoden, Eigenschaften, Felder, Ereignisse, Strukturen und Klassen, um eine App zu erstellen, die nur Code- und Assemblyabhängigkeiten enthält, die zum Ausführen der App erforderlich sind.

Weitere Informationen zum Konfigurieren des Linkerverhaltens finden Sie unter Verknüpfen einer Android-App, Verknüpfen einer iOS-Appund Verknüpfen einer Mac Catalyst-App.

Verringern des App-Aktivierungszeitraums

Alle Apps verfügen über einen Aktivierungszeitraum. Dies ist die Zeit zwischen dem Start der App und dem Zeitpunkt, zu dem die App zur Verwendung bereit ist. Dieser Aktivierungszeitraum bietet den Benutzern ihren ersten Eindruck von der App. Daher ist es wichtig, die Aktivierungszeit und die wahrgenommene Dauer zu verkürzen, damit sie einen positiven ersten Eindruck von der App gewinnen.

Bevor eine App die erste Benutzeroberfläche anzeigt, sollte sie einen Begrüßungsbildschirm bereitstellen, um dem Benutzer anzuzeigen, dass die App gestartet wird. Wenn die App ihre anfängliche Benutzeroberfläche nicht schnell anzeigen kann, sollte der Begrüßungsbildschirm verwendet werden, um den Benutzer während der Aktivierungszeit über den Fortschritt zu informieren und sicherzustellen, dass die App nicht hängen geblieben ist. Diese Bestätigung könnte eine Statusanzeige oder ein ähnliches Steuerelement sein.

Während des Aktivierungszeitraums führen Apps Aktivierungslogik aus, die häufig das Laden und Verarbeiten von Ressourcen umfasst. Der Aktivierungszeitraum kann reduziert werden, indem sichergestellt wird, dass erforderliche Ressourcen innerhalb der App verpackt werden, anstatt remote abgerufen zu werden. In einigen Fällen kann es z. B. während des Aktivierungszeitraums sinnvoll sein, lokal gespeicherte Platzhalterdaten zu laden. Sobald die anfängliche Benutzeroberfläche angezeigt wird und der Benutzer mit der App interagieren kann, können die Platzhalterdaten nach und nach durch Daten von einer entfernten Quelle ersetzt werden. Darüber hinaus sollte die Aktivierungslogik der App nur Arbeit ausführen, die erforderlich ist, damit der Benutzer mit der Verwendung der App beginnen kann. Dies kann hilfreich sein, wenn das Laden zusätzlicher Assemblys verzögert wird, da Assemblys beim ersten Gebrauch geladen werden.

Sorgfältiges Auswählen eines Abhängigkeitseinfügungscontainers

Container für Abhängigkeitseinfügungen führen zusätzliche Leistungseinschränkungen in mobile Apps ein. Das Registrieren und Auflösen von Typen in einem Container verursacht aufgrund der Verwendung von Reflexion zur Erstellung jedes Typs einen Leistungsaufwand, insbesondere wenn Abhängigkeiten für jede Seitennavigation in der App wiederherzustellen sind. Wenn viele oder tiefe Abhängigkeiten vorhanden sind, kann sich die Erstellungskosten erheblich erhöhen. Darüber hinaus kann die Typregistrierung, die in der Regel beim Starten der App auftritt, eine spürbare Auswirkung auf die Startzeit haben, abhängig vom verwendeten Container. Weitere Informationen zur Abhängigkeitsinjektion in .NET MAUI-Apps finden Sie unter Abhängigkeitsinjektion.

Alternativ kann die Abhängigkeitsinjektion durch manuelle Implementierung mithilfe von Fabriken effizienter gestaltet werden.

Erstellen von Shell-Apps

.NET MAUI Shell-Apps bieten eine meinungsbasierte Navigationsoberfläche basierend auf Flyouts und Registerkarten. Wenn Ihre App-Benutzeroberfläche mit Shell implementiert werden kann, ist dies von Vorteil. Shell-Apps tragen dazu bei, ein schlechtes Starterlebnis zu vermeiden, da Seiten bei Bedarf als Reaktion auf die Navigation erstellt werden und nicht bereits beim Start der App, was bei Apps vorkommt, die eine TabbedPageverwenden. Weitere Informationen finden Sie unter Shell-Übersicht.

Optimieren der ListView-Leistung

Bei der Verwendung von ListViewgibt es verschiedene Benutzererfahrungen, die optimiert werden sollten:

  • Initialisierung – das Zeitintervall, das beim Erstellen des Steuerelements beginnt und endet, wenn Elemente auf dem Bildschirm angezeigt werden.
  • Scrollen – die Möglichkeit, durch die Liste zu scrollen und sicherzustellen, dass die Benutzeroberfläche nicht hinter den Berührungsgesten hinterherhinkt.
  • Interaktion zum Hinzufügen, Löschen und Auswählen von Elementen.

Für das ListView-Steuerelement muss eine App Daten und Zellvorlagen bereitstellen. Wie dies erreicht wird, hat einen großen Einfluss auf die Leistung des Steuerelements. Weitere Informationen finden Sie unter Cache-Daten.

Asynchrone Programmierung verwenden

Die Gesamtreaktionsfähigkeit Ihrer App kann verbessert werden, und Leistungsengpässe werden häufig vermieden, indem asynchrone Programmierung verwendet wird. In .NET ist das aufgabenbasiertes asynchrones Muster (TAP) das empfohlene Entwurfsmuster für asynchrone Vorgänge. Die falsche Verwendung der TAP-Anwendung kann jedoch zu unperformanten Apps führen.

Grundlagen

Die folgenden allgemeinen Richtlinien sollten bei Verwendung der TAP befolgt werden:

  • Verstehen Sie den Aufgabenlebenszyklus, der durch die TaskStatus-Aufzählung dargestellt wird. Weitere Informationen finden Sie unter Die Bedeutung von Vorgangsstatus und Vorgangsstatus.
  • Verwenden Sie die Task.WhenAll-Methode, um asynchron zu warten, bis mehrere asynchrone Operationen abgeschlossen sind, anstatt jede asynchrone Operation einzeln mit await auszuführen. Weitere Informationen finden Sie unter Task.WhenAll.
  • Verwenden Sie die Task.WhenAny-Methode, um asynchron zu warten, bis eine von mehreren asynchronen Vorgängen abgeschlossen ist. Weitere Informationen finden Sie in Task.WhenAny.
  • Verwenden Sie die Task.Delay-Methode, um ein Task-Objekt zu erzeugen, das nach der angegebenen Zeit fertiggestellt wird. Dies ist nützlich für Szenarien wie das Abfragen von Daten und das Verzögern der Verarbeitung von Benutzereingaben für eine vorgegebene Zeit. Weitere Informationen finden Sie unter Task.Delay.
  • Führen Sie intensive synchrone CPU-Vorgänge im Threadpool mit der Task.Run-Methode aus. Diese Methode ist eine Abkürzung für die TaskFactory.StartNew-Methode, mit optimal voreingestellten Argumenten. Weitere Informationen finden Sie unter Task.Run.
  • Vermeiden Sie es, asynchrone Konstruktoren zu erstellen. Verwenden Sie stattdessen Lebenszyklusereignisse oder separate Initialisierungslogik, um alle Initialisierungen korrekt await. Weitere Informationen finden Sie unter Async-Konstruktoren auf blog.stephencleary.com.
  • Verwenden Sie das Lazy Task Pattern, um zu vermeiden, dass während des Starts der App auf asynchrone Vorgänge gewartet wird. Weitere Informationen finden Sie unter AsyncLazy.
  • Erstellen Sie einen Aufgabenwrapper für vorhandene asynchrone Vorgänge, die das TAP nicht verwenden, indem Sie TaskCompletionSource<T> Objekte erstellen. Diese Objekte nutzen die Vorteile der Task-Programmierbarkeit und ermöglichen es Ihnen, die Lebensdauer und den Abschluss der zugewiesenen Taskzu kontrollieren. Weitere Informationen finden Sie unter Die Natur von TaskCompletionSource.
  • Geben Sie ein Task-Objekt zurück, anstatt ein erwartetes Task-Objekt zurückzugeben, wenn das Ergebnis eines asynchronen Vorgangs nicht verarbeitet werden muss. Dies ist leistungsfähiger, da weniger Kontextwechsel ausgeführt werden.
  • Verwenden Sie die Task Parallel Library (TPL) Datenflussbibliothek in Szenarien wie der Verarbeitung von Daten, sobald sie verfügbar sind, oder wenn Sie mehrere Vorgänge haben, die asynchron miteinander kommunizieren müssen. Weitere Informationen finden Sie unter Dataflow (Task Parallel Library).

UI

Die folgenden Richtlinien sollten bei der Verwendung von TAP mit UI-Elementen befolgt werden:

  • Rufen Sie eine asynchrone Version einer API auf, falls sie verfügbar ist. Dadurch wird die Blockierung des UI-Threads aufgehoben, wodurch die Benutzererfahrung mit der App verbessert wird.

  • Aktualisieren Sie UI-Elemente mit Daten aus asynchronen Vorgängen im UI-Thread, um zu vermeiden, dass Ausnahmen ausgelöst werden. Aktualisierungen der ListView.ItemsSource-Eigenschaft werden jedoch automatisch an den UI-Thread weitergeleitet. Informationen zum Ermitteln, ob Code im UI-Thread ausgeführt wird, finden Sie unter Erstellen eines Threads im UI-Thread.

    Wichtig

    Alle Steuerelement-Eigenschaften, die über die Datenbindung aktualisiert werden, werden automatisch an den Benutzeroberflächen-Thread umgeleitet.

Fehlerbehandlung

Die folgenden Richtlinien für die Fehlerbehandlung sollten bei Verwendung von TAP befolgt werden:

  • Erfahren Sie mehr über die asynchrone Ausnahmebehandlung. Unbehandelte Ausnahmen, die von Code ausgelöst werden, der asynchron ausgeführt wird, werden mit Ausnahme bestimmter Szenarien an den aufrufenden Thread weitergegeben. Weitere Informationen finden Sie unter Fehlerbehandlung (Task Parallel Library).
  • Vermeiden Sie das Erstellen von async void-Methoden, und erstellen Sie stattdessen async Task-Methoden. Diese ermöglichen eine einfachere Fehlerbehandlung, Zusammenstellung und Testbarkeit. Die Ausnahme von dieser Richtlinie sind asynchrone Ereignishandler, die voidzurückgeben müssen. Weitere Informationen finden Sie unter Avoid Async Void.
  • Kombinieren Sie blockierenden und asynchronen Code nicht, indem Sie die Methoden Task.Wait, Task.Resultoder GetAwaiter().GetResult aufrufen, da sie zu einem Deadlock führen können. Wenn diese Richtlinie jedoch verletzt werden muss, sollte bevorzugt die GetAwaiter().GetResult-Methode aufgerufen werden, da sie die Aufgabenausnahmen bewahrt. Weitere Informationen finden Sie unter Async All the Way und Task Exception Handling in .NET 4.5.
  • Verwenden Sie nach Möglichkeit die ConfigureAwait-Methode, um kontextfreien Code zu erstellen. Kontextfreier Code verfügt über eine bessere Leistung für mobile Apps und ist eine nützliche Technik zum Vermeiden von Deadlocks beim Arbeiten mit einer teilweise asynchronen Codebasis. Weitere Informationen finden Sie unter Configure Context.
  • Verwenden Sie Fortsetzungsaufgaben für Funktionalitäten wie das Behandeln von Ausnahmen, die vom vorherigen asynchronen Vorgang ausgelöst wurden, und das Abbrechen einer Fortsetzung entweder vor ihrem Start oder während ihrer Ausführung. Weitere Informationen finden Sie unter Verketten von Vorgängen mithilfe von fortlaufenden Aufgaben.
  • Verwenden Sie eine asynchrone ICommand Implementierung, wenn asynchrone Vorgänge aus dem ICommandaufgerufen werden. Dadurch wird sichergestellt, dass alle Ausnahmen in der asynchronen Befehlslogik behandelt werden können. Weitere Informationen finden Sie unter Asynchrone Programmierung: Muster für asynchrone MVVM-Anwendungen: Befehle.

Verzögern der Kosten für das Erstellen von Objekten

Lazy-Initialisierung kann verwendet werden, um die Erstellung eines Objekts zu verzögern, bis es zum ersten Mal verwendet wird. Diese Technik wird in erster Linie verwendet, um die Leistung zu verbessern, Berechnungen zu vermeiden und speicheranforderungen zu verringern.

Erwägen Sie die Verwendung von Lazy Initialisierung für Objekte, die in den folgenden Szenarien aufwendig zu erstellen sind:

  • Die App verwendet das Objekt möglicherweise nicht.
  • Andere teure Vorgänge müssen abgeschlossen werden, bevor das Objekt erstellt wird.

Die Lazy<T>-Klasse wird verwendet, um einen träge initialisierten Typ zu definieren, wie im folgenden Beispiel gezeigt:

void ProcessData(bool dataRequired = false)
{
    Lazy<double> data = new Lazy<double>(() =>
    {
        return ParallelEnumerable.Range(0, 1000)
                     .Select(d => Compute(d))
                     .Aggregate((x, y) => x + y);
    });

    if (dataRequired)
    {
        if (data.Value > 90)
        {
            ...
        }
    }
}

double Compute(double x)
{
    ...
}

Lazy-Initialisierung erfolgt beim ersten Zugriff auf die Lazy<T>.Value-Eigenschaft. Der verpackte Typ wird beim ersten Zugriff erstellt und zurückgegeben und für zukünftige Zugriffe gespeichert.

Weitere Informationen zur faulen Initialisierung finden Sie unter Lazy Initialization.

Freigeben von IDisposable-Ressourcen

Die IDisposable-Schnittstelle stellt einen Mechanismus zum Freigeben von Ressourcen bereit. Sie stellt eine Dispose Methode bereit, die implementiert werden sollte, um Ressourcen explizit freizugeben. IDisposable ist kein Destruktor und sollte nur unter folgenden Umständen implementiert werden:

  • Wenn die Klasse nicht verwaltete Ressourcen besitzt. Typische nicht verwaltete Ressourcen, die eine Freigabe erfordern, umfassen Dateien, Datenströme und Netzwerkverbindungen.
  • Wenn die Klasse verwaltete IDisposable Ressourcen enthält.

Typ-Verbraucher können dann die IDisposable.Dispose-Implementierung aufrufen, um Ressourcen freizugeben, wenn die Instanz nicht mehr benötigt wird. Hierfür gibt es zwei Ansätze:

  • Durch Umschließen des IDisposable-Objekts in einer using-Anweisung.
  • Indem Sie den Aufruf von IDisposable.Dispose in einen try/finally Block umschließen.

Umschließen des IDisposable-Objekts in einer using-Anweisung

Das folgende Beispiel zeigt, wie ein IDisposable-Objekt in eine using-Anweisung eingebettet wird:

public void ReadText(string filename)
{
    string text;
    using (StreamReader reader = new StreamReader(filename))
    {
        text = reader.ReadToEnd();
    }
    ...
}

Die StreamReader-Klasse implementiert IDisposable, und die using-Anweisung stellt eine bequeme Syntax bereit, die die StreamReader.Dispose-Methode für das StreamReader-Objekt aufruft, bevor es aus dem Bereich entfernt wird. Innerhalb des using-Blocks ist das StreamReader-Objekt schreibgeschützt und kann nicht neu zugewiesen werden. Die using-Anweisung stellt außerdem sicher, dass die Dispose-Methode aufgerufen wird, auch wenn eine Ausnahme auftritt, da der Compiler die Zwischensprache (IL) für einen try/finally-Block implementiert.

Umschließen Sie den Aufruf von "IDisposable.Dispose" mit einem try/finally-Block.

Das folgende Beispiel zeigt, wie der Aufruf von IDisposable.Dispose in einem try/finally-Block eingeschlossen wird.

public void ReadText(string filename)
{
    string text;
    StreamReader reader = null;
    try
    {
        reader = new StreamReader(filename);
        text = reader.ReadToEnd();
    }
    finally
    {
        if (reader != null)
            reader.Dispose();
    }
    ...
}

Die StreamReader-Klasse implementiert IDisposable, und der finally-Block ruft die StreamReader.Dispose-Methode auf, um die Ressource freizugeben. Weitere Informationen finden Sie unter IDisposable Interface.

Abmelden von Ereignissen

Um Speicherverluste zu verhindern, sollten Ereignisse abgemeldet werden, bevor das Abonnentenobjekt verworfen wird. Bis das Ereignis abgemeldet wurde, hat der Delegat für das Ereignis im publizierenden Objekt einen Verweis auf den Delegaten, der den Ereignishandler des Abonnenten kapselt. Solange das Veröffentlichungsobjekt diesen Verweis enthält, gibt die Garbage Collection den Speicher des Abonnentenobjekts nicht zurück.

Das folgende Beispiel zeigt, wie ein Abonnement von einem Ereignis abbestellt wird.

public class Publisher
{
    public event EventHandler MyEvent;

    public void OnMyEventFires()
    {
        if (MyEvent != null)
            MyEvent(this, EventArgs.Empty);
    }
}

public class Subscriber : IDisposable
{
    readonly Publisher _publisher;

    public Subscriber(Publisher publish)
    {
        _publisher = publish;
        _publisher.MyEvent += OnMyEventFires;
    }

    void OnMyEventFires(object sender, EventArgs e)
    {
        Debug.WriteLine("The publisher notified the subscriber of an event");
    }

    public void Dispose()
    {
        _publisher.MyEvent -= OnMyEventFires;
    }
}

Die Subscriber-Klasse meldet sich vom Ereignis in ihrer Dispose-Methode ab.

Referenzzyklen können auch auftreten, wenn Ereignishandler und Lambda-Syntax verwendet werden, da Lambda-Ausdrücke auf Objekte verweisen und aktiv bleiben können. Daher kann ein Verweis auf die anonyme Methode in einem Feld gespeichert und zum Abmelden von dem Ereignis verwendet werden, wie im folgenden Beispiel gezeigt:

public class Subscriber : IDisposable
{
    readonly Publisher _publisher;
    EventHandler _handler;

    public Subscriber(Publisher publish)
    {
        _publisher = publish;
        _handler = (sender, e) =>
        {
            Debug.WriteLine("The publisher notified the subscriber of an event");
        };
        _publisher.MyEvent += _handler;
    }

    public void Dispose()
    {
        _publisher.MyEvent -= _handler;
    }
}

Das Feld _handler verwaltet den Verweis auf die anonyme Methode und wird zum Abonnieren und Abbestellen von Ereignissen verwendet.

Vermeiden Sie starke zirkuläre Referenzen auf iOS und Mac Catalyst

In einigen Situationen ist es möglich, starke Referenzzyklen zu erstellen, die verhindern können, dass Objekte ihren Speicher vom Garbage Collector zurückgefordert haben. Betrachten Sie beispielsweise den Fall, in dem eine von NSObjectabgeleitete Unterklasse, z. B. eine Klasse, die von UIViewerbt, einem NSObject-abgeleiteten Container hinzugefügt wird und stark von Objective-Creferenziert wird, wie im folgenden Beispiel gezeigt:

class Container : UIView
{
    public void Poke()
    {
        // Call this method to poke this object
    }
}

class MyView : UIView
{
    Container _parent;

    public MyView(Container parent)
    {
        _parent = parent;
    }

    void PokeParent()
    {
        _parent.Poke();
    }
}

var container = new Container();
container.AddSubview(new MyView(container));

Wenn dieser Code die Container Instanz erstellt, weist das C#-Objekt einen starken Verweis auf ein Objective-C-Objekt auf. Ebenso wird die MyView-Instanz auch einen starken Verweis auf ein Objective-C-Objekt haben.

Darüber hinaus erhöht der Aufruf von container.AddSubview den Referenzzähler der nicht verwalteten Instanz MyView. In diesem Fall erstellt die .NET für iOS-Laufzeit eine GCHandle Instanz, um das MyView-Objekt im verwalteten Code lebendig zu halten, da keine Garantie dafür besteht, dass verwaltete Objekte einen Verweis darauf erhalten. Aus Sicht von verwaltetem Code würde das MyView-Objekt nach dem Aufruf von AddSubview(UIView) zurückgewonnen werden, wenn es nicht das GCHandlegäbe.

Das nicht verwaltete MyView-Objekt weist ein GCHandle auf das verwaltete Objekt hin, das als starke Verknüpfungbezeichnet wird. Das verwaltete Objekt enthält einen Verweis auf die Container Instanz. Wiederum verfügt die Container Instanz über einen verwalteten Verweis auf das MyView-Objekt.

In Fällen, in denen ein enthaltenes Objekt eine Verknüpfung mit seinem Container behält, gibt es mehrere Optionen, um die zyklische Referenz zu behandeln.

  • Vermeiden Sie den Zirkelbezug, indem Sie einen schwachen Verweis auf den Container verwenden.
  • Rufen Sie Dispose für die Objekte auf.
  • Unterbrechen Sie den Zyklus manuell, indem Sie den Link zum Container auf nullfestlegen.
  • Entfernen Sie das enthaltene Objekt manuell aus dem Container.

Schwache Verweise verwenden

Eine Möglichkeit, einen Zyklus zu verhindern, besteht darin, einen schwachen Verweis vom untergeordneten Element auf das übergeordnete Element zu verwenden. Der obige Code könnte z. B. wie im folgenden Beispiel aussehen:

class Container : UIView
{
    public void Poke()
    {
        // Call this method to poke this object
    }
}

class MyView : UIView
{
    WeakReference<Container> _weakParent;

    public MyView(Container parent)
    {
        _weakParent = new WeakReference<Container>(parent);
    }

    void PokeParent()
    {
        if (weakParent.TryGetTarget (out var parent))
            parent.Poke();
    }
}

var container = new Container();
container.AddSubview(new MyView container));

Hier behält das enthaltene Objekt das übergeordnete Objekt nicht am Leben. Der Elternteil hält das Kind jedoch durch den Aufruf von container.AddSubViewlebendig.

Dies geschieht auch in iOS-APIs, die das Delegat- oder Datenquellenmuster verwenden, bei denen eine Peerklasse die Implementierung enthält. Wenn Sie beispielsweise die eigenschaft Delegate oder die DataSource in der UITableView Klasse festlegen.

Im Fall von Klassen, die ausschließlich zum Implementieren eines Protokolls erstellt werden, z. B. der IUITableViewDataSource, können Sie, anstatt eine Unterklasse zu erstellen, einfach die Schnittstelle in der Klasse implementieren, die Methode überschreiben und die Eigenschaft DataSource auf thiszuweisen.

Löschen von Objekten mit starken Verweisen

Wenn ein starker Verweis vorhanden ist und es schwierig ist, die Abhängigkeit zu entfernen, sollte eine Dispose-Methode den übergeordneten Zeiger zurücksetzen.

Setzen Sie für Container die Dispose Methode außer Kraft, um die enthaltenen Objekte zu entfernen, wie im folgenden Beispiel gezeigt:

class MyContainer : UIView
{
    public override void Dispose()
    {
        // Brute force, remove everything
        foreach (var view in Subviews)
        {
              view.RemoveFromSuperview();
        }
        base.Dispose();
    }
}

Für ein untergeordnetes Objekt, das einen starken Verweis auf das übergeordnete Objekt behält, löschen Sie den Verweis auf das übergeordnete Objekt in der Dispose Implementierung.

class MyChild : UIView
{
    MyContainer _container;

    public MyChild(MyContainer container)
    {
        _container = container;
    }

    public override void Dispose()
    {
        _container = null;
    }
}