Praca z wątkiem interfejsu użytkownika na platformie Xamarin.iOS

Interfejsy użytkownika aplikacji są zawsze jednowątkowy, nawet w urządzeniach wielowątkowych — istnieje tylko jedna reprezentacja ekranu i wszelkie zmiany wyświetlanego elementu muszą być koordynowane za pośrednictwem jednego "punktu dostępu". Zapobiega to jednoczesnej próbie zaktualizowania tego samego piksela przez wiele wątków (na przykład).

Kod powinien wprowadzać zmiany tylko w kontrolkach interfejsu użytkownika z wątku głównego (lub interfejsu użytkownika). Wszelkie aktualizacje interfejsu użytkownika, które występują w innym wątku (takim jak wywołanie zwrotne lub wątek w tle), mogą nie zostać renderowane na ekranie lub nawet spowodować awarię.

Wykonywanie wątku interfejsu użytkownika

Podczas tworzenia kontrolek w widoku lub obsługi zdarzenia zainicjowanego przez użytkownika, takiego jak dotknięcie, kod jest już wykonywany w kontekście wątku interfejsu użytkownika.

Jeśli kod jest wykonywany w wątku w tle, w zadaniu lub wywołaniu zwrotnym prawdopodobnie nie jest wykonywany w głównym wątku interfejsu użytkownika. W takim przypadku należy opakować kod w wywołaniu metody InvokeOnMainThread lub BeginInvokeOnMainThread w następujący sposób:

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

Metoda jest zdefiniowana w NSObject elemecie InvokeOnMainThread , więc można ją wywołać z metod zdefiniowanych na dowolnym obiekcie UIKit (takim jak Widok lub Kontroler widoku).

Podczas debugowania aplikacji platformy Xamarin.iOS zostanie zgłoszony błąd, jeśli kod próbuje uzyskać dostęp do kontrolki interfejsu użytkownika z nieprawidłowego wątku. Ułatwia to śledzenie i rozwiązywanie tych problemów za pomocą metody InvokeOnMainThread. Dzieje się tak tylko podczas debugowania i nie zgłasza błędu w kompilacjach wydania. Komunikat o błędzie będzie wyświetlany następująco:

UI Thread Execution

Przykład wątku tła

Oto przykład, który próbuje uzyskać dostęp do kontrolki interfejsu użytkownika (a UILabel) z wątku w tle przy użyciu prostego wątku:

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

Ten kod zgłosi błąd UIKitThreadAccessException podczas debugowania. Aby rozwiązać ten problem (i upewnić się, że kontrola interfejsu użytkownika jest dostępna tylko z głównego wątku interfejsu użytkownika), opakuj dowolny kod odwołujący się do kontrolek interfejsu użytkownika wewnątrz InvokeOnMainThread wyrażenia w następujący sposób:

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

Nie trzeba tego używać w pozostałej części przykładów w tym dokumencie, ale ważne jest, aby pamiętać, kiedy aplikacja wysyła żądania sieciowe, używa centrum powiadomień lub innych metod wymagających obsługi ukończenia, która będzie uruchamiana w innym wątku.

Przykład Async/Await

W przypadku używania słów kluczowych asynchronicznych InvokeOnMainThread /await w języku C# 5 nie jest wymagane, ponieważ w przypadku ukończenia oczekiwanego zadania metoda będzie kontynuowana w wątku wywołującym.

Ten przykładowy kod (który oczekuje na wywołanie metody Delay, wyłącznie w celach demonstracyjnych) pokazuje metodę asynchroniczną wywoływaną w wątku interfejsu użytkownika (jest to procedura obsługi TouchUpInside). Ponieważ metoda zawierająca jest wywoływana w wątku interfejsu użytkownika, operacje interfejsu użytkownika, takie jak ustawianie tekstu na UILabel obiekcie lub wyświetlanie UIAlertView elementu, można bezpiecznie wywołać po zakończeniu operacji asynchronicznych w wątkach w tle.

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";
}

Jeśli metoda asynchronikowa jest wywoływana z wątku w tle (a nie głównego wątku interfejsu użytkownika), InvokeOnMainThread nadal będzie wymagana.