Работа с потоком пользовательского интерфейса в Xamarin.iOS

Пользовательские интерфейсы приложений всегда однопоточные, даже на многопоточных устройствах— есть только одно представление экрана и все изменения, которые отображаются, должны быть согласованы через одну "точку доступа". Это предотвращает одновременное обновление нескольких потоков в одном и том же пикселе (например).

Код должен вносить изменения только в элементы управления пользовательским интерфейсом из основного потока (или пользовательского интерфейса). Любые обновления пользовательского интерфейса, происходящие в другом потоке (например, обратном вызове или фоновом потоке), могут не отображаться на экране или даже могут привести к сбою.

Выполнение потока пользовательского интерфейса

При создании элементов управления в представлении или обработке события, инициированного пользователем, например касания, код уже выполняется в контексте потока пользовательского интерфейса.

Если код выполняется в фоновом потоке, в задаче или обратном вызове, скорее всего, он не выполняется в основном потоке пользовательского интерфейса. В этом случае следует упаковать код в вызов InvokeOnMainThread или BeginInvokeOnMainThread примерно так:

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

Метод InvokeOnMainThread определяется так NSObject , чтобы его можно было вызывать из методов, определенных для любого объекта UIKit (например, view или View Controller).

При отладке приложений Xamarin.iOS возникает ошибка, если код пытается получить доступ к элементу управления пользовательским интерфейсом из неправильного потока. Это помогает отслеживать и устранять эти проблемы с помощью метода InvokeOnMainThread. Это происходит только при отладке и не вызывает ошибку в сборках выпуска. Сообщение об ошибке появится следующим образом:

UI Thread Execution

Пример фонового потока

Ниже приведен пример, который пытается получить доступ к элементу управления пользовательским интерфейсом (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 то все равно потребуется.