Partilhar via


Trabalhando com o thread da interface do usuário no Xamarin.iOS

As interfaces de usuário do aplicativo são sempre single-threaded, mesmo em dispositivos multi-threaded – há apenas uma representação da tela e quaisquer alterações no que é exibido precisam ser coordenadas através de um único "ponto de acesso". Isso impede que vários threads tentem atualizar o mesmo pixel ao mesmo tempo (por exemplo).

Seu código só deve fazer alterações nos controles da interface do usuário a partir do thread principal (ou da interface do usuário). Quaisquer atualizações de interface do usuário que ocorram em um thread diferente (como um retorno de chamada ou thread em segundo plano) podem não ser renderizadas na tela ou podem até causar uma falha.

Execução de thread da interface do usuário

Quando você está criando controles em um modo de exibição ou manipulando um evento iniciado pelo usuário, como um toque, o código já está sendo executado no contexto do thread da interface do usuário.

Se o código estiver sendo executado em um thread em segundo plano, em uma tarefa ou em um retorno de chamada, é provável que ele NÃO esteja sendo executado no thread principal da interface do usuário. Nesse caso, você deve encapsular o código em uma chamada para InvokeOnMainThread ou BeginInvokeOnMainThread como esta:

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

O InvokeOnMainThread método é definido em NSObject para que possa ser chamado de dentro de métodos definidos em qualquer objeto UIKit (como um View ou View Controller).

Ao depurar aplicativos Xamarin.iOS, um erro será gerado se o código tentar acessar um controle de interface do usuário a partir do thread errado. Isso ajuda você a rastrear e corrigir esses problemas com o método InvokeOnMainThread. Isso só ocorre durante a depuração e não gera um erro nas compilações de versão. A mensagem de erro aparecerá assim:

Execução de thread da interface do usuário

Exemplo de thread em segundo plano

Aqui está um exemplo que tenta acessar um controle de interface do usuário (a UILabel) de um thread em segundo plano usando um thread simples:

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

Esse código lançará a UIKitThreadAccessException depuração while. Para corrigir o problema (e garantir que o controle da interface do usuário seja acessado somente a partir do thread principal da interface do usuário), encapsular qualquer código que faça referência a controles da interface do usuário dentro de uma InvokeOnMainThread expressão como esta:

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

Você não precisará usar isso para o restante dos exemplos neste documento, mas é um conceito importante a ser lembrado quando seu aplicativo faz solicitações de rede, usa a central de notificações ou outros métodos que exigem um manipulador de conclusão que será executado em outro thread.

Exemplo de Async/Await

Ao usar o C# 5, as palavras-chave InvokeOnMainThread async/await não são necessárias porque quando uma tarefa aguardada é concluída, o método continua no thread de chamada.

Este código de exemplo (que aguarda em uma chamada de método Delay, puramente para fins de demonstração) mostra um método assíncrono que é chamado no thread da interface do usuário (é um manipulador TouchUpInside). Como o método que contém é chamado no thread da interface do usuário, as operações da interface do usuário, como definir o texto em um UILabel ou mostrar um podem ser chamadas com segurança depois que as UIAlertView operações assíncronas forem concluídas em threads em segundo plano.

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 um método assíncrono for chamado a partir de um thread em segundo plano (não o thread principal da interface do usuário), ainda InvokeOnMainThread será necessário.