Работа с потоком пользовательского интерфейса в Xamarin.iOS
Пользовательские интерфейсы приложений всегда однопоточные, даже на многопоточных устройствах— есть только одно представление экрана и все изменения, которые отображаются, должны быть согласованы через одну "точку доступа". Это предотвращает одновременное обновление нескольких потоков в одном и том же пикселе (например).
Код должен вносить изменения только в элементы управления пользовательским интерфейсом из основного потока (или пользовательского интерфейса). Любые обновления пользовательского интерфейса, происходящие в другом потоке (например, обратном вызове или фоновом потоке), могут не отображаться на экране или даже могут привести к сбою.
Выполнение потока пользовательского интерфейса
При создании элементов управления в представлении или обработке события, инициированного пользователем, например касания, код уже выполняется в контексте потока пользовательского интерфейса.
Если код выполняется в фоновом потоке, в задаче или обратном вызове, скорее всего, он не выполняется в основном потоке пользовательского интерфейса. В этом случае следует упаковать код в вызов InvokeOnMainThread
или BeginInvokeOnMainThread
примерно так:
InvokeOnMainThread ( () => {
// manipulate UI controls
});
Метод InvokeOnMainThread
определяется так NSObject
, чтобы его можно было вызывать из методов, определенных для любого объекта UIKit (например, view или View Controller).
При отладке приложений Xamarin.iOS возникает ошибка, если код пытается получить доступ к элементу управления пользовательским интерфейсом из неправильного потока. Это помогает отслеживать и устранять эти проблемы с помощью метода InvokeOnMainThread. Это происходит только при отладке и не вызывает ошибку в сборках выпуска. Сообщение об ошибке появится следующим образом:
Пример фонового потока
Ниже приведен пример, который пытается получить доступ к элементу управления пользовательским интерфейсом (a UILabel
) из фонового потока с помощью простого потока:
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
label1.Text = "updated in thread"; // should NOT reference UILabel on background thread!
})).Start();
Этот код создает исключение UIKitThreadAccessException
во время отладки. Чтобы устранить проблему (и убедиться, что элемент управления пользовательским интерфейсом доступен только из основного потока пользовательского интерфейса), обтекайте любой код, ссылающийся на элементы управления пользовательского InvokeOnMainThread
интерфейса внутри выражения, как показано ниже:
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
InvokeOnMainThread (() => {
label1.Text = "updated in thread"; // this works!
});
})).Start();
Вам не нужно использовать это для остальных примеров в этом документе, но важно помнить, когда приложение выполняет сетевые запросы, использует центр уведомлений или другие методы, требующие обработчика завершения, который будет выполняться в другом потоке.
Пример Async/Await
При использовании асинхронных и ожидающих ключевое слово InvokeOnMainThread
C# 5 не требуется, так как при завершении ожидаемой задачи метод продолжается в вызывающем потоке.
В этом примере кода (который ожидает вызов метода Delay, исключительно для демонстрационных целей) показан асинхронный метод, который вызывается в потоке пользовательского интерфейса (это обработчик TouchUpInside). Так как содержащий метод вызывается в потоке пользовательского интерфейса, операции пользовательского интерфейса, такие как установка текста на объекте UILabel
или отображение UIAlertView
его можно безопасно вызвать после выполнения асинхронных операций в фоновых потоках.
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";
}
Если асинхронный метод вызывается из фонового потока (а не основного потока пользовательского интерфейса), InvokeOnMainThread
то все равно потребуется.