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:
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.