Práce s vláknem uživatelského rozhraní v Xamarin.iOS

Uživatelská rozhraní aplikací jsou vždy jednovláknová, dokonce i ve vícevláknových zařízeních – existuje jenom jedna reprezentace obrazovky a všechny změny toho, co se zobrazí, je potřeba koordinovat prostřednictvím jediného přístupového bodu. Zabráníte tomu, aby se několik vláken pokusilo aktualizovat stejný pixel současně (například).

Kód by měl provádět pouze změny ovládacích prvků uživatelského rozhraní z hlavního vlákna (nebo uživatelského rozhraní). Všechny aktualizace uživatelského rozhraní, ke kterým dochází v jiném vlákně (například zpětném volání nebo vlákně na pozadí), se nemusí vykreslit na obrazovku nebo dokonce způsobit chybové ukončení.

Spuštění vlákna uživatelského rozhraní

Při vytváření ovládacích prvků v zobrazení nebo zpracování události iniciované uživatelem, například dotykového ovládání, kód se už spouští v kontextu vlákna uživatelského rozhraní.

Pokud se kód spouští na vlákně na pozadí, v úloze nebo zpětném volání, pravděpodobně se neskutečuje na hlavním vlákně uživatelského rozhraní. V takovém případě byste měli kód zabalit do volání InvokeOnMainThread nebo BeginInvokeOnMainThread takto:

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

Metoda je definována InvokeOnMainThreadNSObject tak, aby ji bylo možné volat z metod definovaných u libovolného objektu UIKit (například zobrazení nebo kontroleru zobrazení).

Při ladění aplikací Xamarin.iOS dojde k chybě, pokud se váš kód pokusí získat přístup k ovládacímu prvku uživatelského rozhraní z nesprávného vlákna. To vám pomůže zjistit a opravit tyto problémy s InvokeOnMainThread metoda. K tomu dochází pouze při ladění a nevyvolá chybu v buildech vydaných verzí. Chybová zpráva se zobrazí takto:

UI Thread Execution

Příklad vlákna na pozadí

Tady je příklad, který se pokusí získat přístup k ovládacímu prvku uživatelského rozhraní (a UILabel) z vlákna na pozadí pomocí jednoduchého vlákna:

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

Tento kód vyvolá UIKitThreadAccessException během ladění. Pokud chcete problém vyřešit (a ujistěte se, že se k ovládacímu prvku uživatelského rozhraní přistupuje jenom z hlavního vlákna uživatelského rozhraní), zabalte veškerý kód, který odkazuje na ovládací prvky uživatelského rozhraní uvnitř výrazu InvokeOnMainThread takto:

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

Nebudete ho muset použít pro zbývající příklady v tomto dokumentu, ale je důležité si uvědomit, kdy vaše aplikace provádí síťové požadavky, používá centrum oznámení nebo jiné metody, které vyžadují obslužnou rutinu dokončení, která se spustí v jiném vlákně.

Příklad Async/Await

Při použití klíčových slov InvokeOnMainThread asynchronního/await jazyka C# 5 se nevyžaduje, protože po dokončení očekávané úlohy bude metoda pokračovat ve volajícím vlákně.

Tento ukázkový kód (který čeká na volání metody Delay, čistě pro demonstrační účely) ukazuje asynchronní metodu, která se volá na vlákně uživatelského rozhraní (jedná se o obslužnou rutinu TouchUpInside). Vzhledem k tomu, že se na vlákně uživatelského rozhraní volá metoda obsahující, operace uživatelského rozhraní, jako je nastavení textu na nebo UILabel zobrazení UIAlertView , se dá bezpečně volat po dokončení asynchronních operací na vláknech na pozadí.

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

Pokud se asynchronní metoda volá z vlákna na pozadí (ne z hlavního vlákna uživatelského rozhraní), InvokeOnMainThread bude stále potřeba.