Sdílet prostřednictvím


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

Uživatelská rozhraní aplikací jsou vždy jednovláknová, a to i v zařízeních s více vlákny – existuje pouze jedna reprezentace obrazovky a všechny změny zobrazeného obsahu musí být sladěné prostřednictvím jediného "přístupového bodu". Zabráníte tak tomu, aby se několik vláken pokusilo aktualizovat stejný pixel současně (například).

Kód by měl provádět změny pouze 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í.

Spouštění vláken uživatelského rozhraní

Když vytváříte ovládací prvky v zobrazení nebo zpracováváte událost iniciovanou uživatelem, jako je dotykové ovládání, kód se už spouští v kontextu vlákna uživatelského rozhraní.

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

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

Metoda je definována InvokeOnMainThread tak NSObject , aby ji bylo možné volat z metod definovaných v libovolném objektu UIKit (například View nebo View Controller).

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

Spouštění vláken uživatelského rozhraní

Příklad vlákna na pozadí

Tady je příklad, který se pokusí o 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 tento 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 , například 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 to důležitý koncept, který si pamatujete, když vaše aplikace vytváří 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í/await jazyka C# 5 není vyžadováno, protože když očekávaný úkol dokončí metodu pokračuje 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á je volána ve vlákně uživatelského rozhraní (jedná se o obslužnou rutinu TouchUpInside). Vzhledem k tomu, že obsahující metoda je volána ve vlákně uživatelského rozhraní, operace uživatelského rozhraní, jako je nastavení textu na UILabel nebo zobrazení UIAlertView , lze bezpečně volat po dokončení asynchronních operací na podprocesech 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 je asynchronní metoda volána z vlákna na pozadí (nikoli hlavní vlákno uživatelského rozhraní), InvokeOnMainThread bude stále nutné.