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:
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.