Condividi tramite


Uso del thread dell'interfaccia utente in Xamarin.iOS

Le interfacce utente dell'applicazione sono sempre a thread singolo, anche nei dispositivi multithread, ovvero una sola rappresentazione dello schermo e le modifiche apportate a ciò che viene visualizzato devono essere coordinate tramite un singolo "punto di accesso". Ciò impedisce a più thread di provare ad aggiornare lo stesso pixel contemporaneamente (ad esempio).

Il codice deve apportare modifiche solo ai controlli dell'interfaccia utente dal thread principale (o dell'interfaccia utente). Gli aggiornamenti dell'interfaccia utente che si verificano in un thread diverso , ad esempio un callback o un thread in background, potrebbero non essere visualizzati sullo schermo o potrebbero causare un arresto anomalo.

Esecuzione di thread dell'interfaccia utente

Quando si creano controlli in una visualizzazione o si gestisce un evento avviato dall'utente, ad esempio un tocco, il codice è già in esecuzione nel contesto del thread dell'interfaccia utente.

Se il codice viene eseguito in un thread in background, in un'attività o in un callback, è probabile che non venga eseguito nel thread principale dell'interfaccia utente. In questo caso, è necessario eseguire il wrapping del codice in una chiamata a InvokeOnMainThread o BeginInvokeOnMainThread in questo modo:

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

Il InvokeOnMainThread metodo viene definito in NSObject modo che possa essere chiamato dall'interno di metodi definiti in qualsiasi oggetto UIKit ,ad esempio un view o un controller di visualizzazione.

Durante il debug delle applicazioni Xamarin.iOS, viene generato un errore se il codice tenta di accedere a un controllo dell'interfaccia utente dal thread errato. Ciò consente di rilevare e risolvere questi problemi con il metodo InvokeOnMainThread. Ciò si verifica solo durante il debug e non genera un errore nelle build di versione. Il messaggio di errore verrà visualizzato come segue:

Esecuzione di thread dell'interfaccia utente

Esempio di thread in background

Di seguito è riportato un esempio che tenta di accedere a un controllo dell'interfaccia utente (a UILabel) da un thread in background usando un thread semplice:

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

Il codice genererà durante il UIKitThreadAccessException debug. Per risolvere il problema e assicurarsi che il controllo dell'interfaccia utente sia accessibile solo dal thread principale dell'interfaccia utente, eseguire il wrapping di qualsiasi codice che faccia riferimento ai controlli dell'interfaccia utente all'interno di un'espressione InvokeOnMainThread simile al seguente:

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

Non è necessario usarlo per il resto degli esempi di questo documento, ma è un concetto importante ricordare quando l'app effettua richieste di rete, usa il centro notifiche o altri metodi che richiedono un gestore di completamento che verrà eseguito su un altro thread.

Esempio di Async/Await

Quando si usano le parole chiave InvokeOnMainThread async/await C# 5 non è necessario perché quando un'attività attesa completa il metodo continua nel thread chiamante.

Questo codice di esempio (che attende in una chiamata al metodo Delay, esclusivamente a scopo dimostrativo) mostra un metodo asincrono chiamato nel thread dell'interfaccia utente (si tratta di un gestore TouchUpInside). Poiché il metodo contenitore viene chiamato nel thread dell'interfaccia utente, le operazioni dell'interfaccia utente come l'impostazione del testo su un UILabel oggetto o la visualizzazione di un UIAlertView oggetto possono essere chiamate in modo sicuro dopo il completamento delle operazioni asincrone nei thread in background.

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

Se un metodo asincrono viene chiamato da un thread in background (non dal thread principale dell'interfaccia utente), InvokeOnMainThread sarebbe comunque necessario.