Arbeiten mit dem UI-Thread in Xamarin.iOS

Anwendungsbenutzeroberflächen sind immer single-threaded, auch bei Multithreadgeräten. Es gibt nur eine Darstellung des Bildschirms, und alle Änderungen an den Angezeigten müssen über einen einzigen "Zugriffspunkt" koordiniert werden. Dadurch wird verhindert, dass mehrere Threads versuchen, dasselbe Pixel gleichzeitig zu aktualisieren (z. B. ).

Ihr Code sollte nur Änderungen an Steuerelementen der Benutzeroberfläche über den Standard-Thread (oder ui) vornehmen. Alle Benutzeroberflächenupdates, die für einen anderen Thread (z. B. ein Rückruf oder Hintergrundthread) auftreten, werden möglicherweise nicht auf dem Bildschirm gerendert oder können sogar zu einem Absturz führen.

Ausführung des UI-Threads

Wenn Sie Steuerelemente in einer Ansicht erstellen oder ein vom Benutzer initiiertes Ereignis wie eine Toucheingabe behandeln, wird der Code bereits im Kontext des UI-Threads ausgeführt.

Wenn Code in einem Hintergrundthread, in einer Aufgabe oder einem Rückruf ausgeführt wird, wird er wahrscheinlich NICHT auf dem Standard UI-Thread ausgeführt. In diesem Fall sollten Sie den Code in einen Aufruf von InvokeOnMainThread oder BeginInvokeOnMainThread wie folgt umschließen:

InvokeOnMainThread ( () => {
    // manipulate UI controls
});

Die InvokeOnMainThread -Methode ist für definiert NSObject , sodass sie innerhalb von Methoden aufgerufen werden kann, die für ein beliebiges UIKit-Objekt (z. B. ein View- oder View-Controller) definiert sind.

Beim Debuggen von Xamarin.iOS-Anwendungen wird ein Fehler ausgelöst, wenn Ihr Code versucht, über den falschen Thread auf ein UI-Steuerelement zuzugreifen. Dies hilft Ihnen, diese Probleme mit der InvokeOnMainThread-Methode aufzuspüren und zu beheben. Dies tritt nur beim Debuggen auf und löst keinen Fehler in Releasebuilds aus. Die Fehlermeldung wird wie folgt angezeigt:

Ausführung des UI-Threads

Hintergrundthreadbeispiel

Hier sehen Sie ein Beispiel, das versucht, über einen Hintergrundthread mithilfe eines einfachen Threads auf ein Benutzeroberflächensteuerelement (a UILabel) zuzugreifen:

new System.Threading.Thread(new System.Threading.ThreadStart(() => {
    label1.Text = "updated in thread"; // should NOT reference UILabel on background thread!
})).Start();

Dieser Code löst das UIKitThreadAccessException beim Debuggen aus. Um das Problem zu beheben (und sicherzustellen, dass nur über den Standard UI-Thread auf das Steuerelement der Benutzeroberfläche zugegriffen wird), umschließen Sie code, der auf Benutzeroberflächensteuerelemente in einem InvokeOnMainThread Ausdruck wie folgt verweist:

new System.Threading.Thread(new System.Threading.ThreadStart(() => {
    InvokeOnMainThread (() => {
        label1.Text = "updated in thread"; // this works!
    });
})).Start();

Sie müssen dies nicht für den Rest der Beispiele in diesem Dokument verwenden, aber es ist ein wichtiges Konzept, sich daran zu erinnern, wenn Ihre App Netzwerkanforderungen sendet, das Notification Center oder andere Methoden verwendet, die einen Vervollständigungshandler erfordern, der in einem anderen Thread ausgeführt wird.

Beispiel für Async/Await

Bei Verwendung der C# 5-Schlüsselwörter InvokeOnMainThread "async/await" ist nicht erforderlich, da die Methode im aufrufenden Thread fortgesetzt wird, wenn eine erwartete Aufgabe abgeschlossen ist.

Dieser Beispielcode (der für einen Aufruf der Delay-Methode wartet, rein zu Demonstrationszwecken) zeigt eine asynchrone Methode, die im UI-Thread aufgerufen wird (es handelt sich um einen TouchUpInside-Handler). Da die enthaltende Methode im UI-Thread aufgerufen wird, können UI-Vorgänge wie das Festlegen des Texts auf einem UILabel oder das Anzeigen eines UIAlertView sicher aufgerufen werden, nachdem asynchrone Vorgänge für Hintergrundthreads abgeschlossen wurden.

async partial void button2_TouchUpInside (UIButton sender)
{
    textfield1.ResignFirstResponder ();
    textfield2.ResignFirstResponder ();
    textview1.ResignFirstResponder ();
    label1.Text = "async method started";
    await Task.Delay(1000); // example purpose only
    label1.Text = "1 second passed";
    await Task.Delay(2000);
    label1.Text = "2 more seconds passed";
    await Task.Delay(1000);
    new UIAlertView("Async method complete", "This method", 
               null, "Cancel", null)
        .Show();
    label1.Text = "async method completed";
}

Wenn eine asynchrone Methode aus einem Hintergrundthread (nicht der Standard UI-Thread) InvokeOnMainThread aufgerufen wird, ist weiterhin erforderlich.