Então, algo está me incomodando sobre o novo suporte assíncrono em C # 5:
O usuário pressiona um botão que inicia uma operação assíncrona. A chamada retorna imediatamente e a bomba de mensagens começa a funcionar novamente - esse é o ponto.
Assim, o usuário pode pressionar o botão novamente - causando reentrada. E se isso for um problema?
Nas demos que eu vi, elas desativam o botão antes da await
chamada e o ativam novamente depois. Isso me parece uma solução muito frágil em um aplicativo do mundo real.
Devemos codificar algum tipo de máquina de estado que especifique quais controles devem ser desabilitados para um determinado conjunto de operações em execução? Ou há um jeito melhor?
Estou tentado a mostrar apenas uma caixa de diálogo modal durante a operação, mas parece um pouco como usar uma marreta.
Alguém tem alguma idéia brilhante, por favor?
EDITAR:
Eu acho que desabilitar os controles que não devem ser usados enquanto uma operação está em execução é frágil, porque acho que rapidamente se tornará complexo quando você tiver uma janela com muitos controles. Eu gosto de manter as coisas simples porque reduz a chance de erros, tanto durante a codificação inicial quanto na manutenção subsequente.
E se houver uma coleção de controles que devem ser desabilitados para uma operação específica? E se várias operações estiverem sendo executadas simultaneamente?
fonte
Respostas:
Vamos começar observando que isso já é um problema, mesmo sem o suporte assíncrono no idioma. Muitas coisas podem fazer com que as mensagens sejam desenfileiradas enquanto você lida com um evento. Você já precisa codificar defensivamente para esta situação; o novo recurso de idioma apenas torna isso mais óbvio , que é a bondade.
A fonte mais comum de um loop de mensagens em execução durante um evento que causa reentrada indesejada é
DoEvents
. O lado bom,await
ao contrário,DoEvents
é queawait
aumenta a probabilidade de novas tarefas não "morrerem de fome" nas tarefas atualmente em execução.DoEvents
bombeia o loop de mensagem e, em seguida, chama de forma síncrona o manipulador de eventos reentrante, enquantoawait
normalmente enfileira a nova tarefa para que ela seja executada em algum momento no futuro.Você pode gerenciar essa complexidade da mesma maneira que gerencia qualquer outra complexidade em uma linguagem OO: abstrai os mecanismos em uma classe e torna a classe responsável pela implementação correta de uma política . (Tente manter os mecanismos logicamente distintos da política; deve ser possível mudar a política sem mexer demais com os mecanismos.)
Se você possui um formulário com muitos controles que interagem de maneiras interessantes, então você está vivendo em um mundo de complexidade que você mesmo criou . Você precisa escrever um código para gerenciar essa complexidade; se você não gostar, simplifique o formulário para que não seja tão complexo.
Os usuários vão odiar isso.
fonte
Por que você acha que desativar o botão antes do
await
e depois reativá-lo quando a chamada é frágil? É porque você está preocupado que o botão nunca seja reativado?Bem, se a chamada não retornar, você não poderá fazer a ligação novamente, então parece que esse é o comportamento que você deseja. Isso indica um estado de erro que você deve corrigir. Se sua chamada assíncrona atingir o tempo limite, isso ainda poderá deixar seu aplicativo em um estado indeterminado - novamente no qual ter um botão ativado pode ser perigoso.
A única vez que isso pode ficar confuso é se houver várias operações que afetam o estado de um botão, mas acho que essas situações devem ser muito raras.
fonte
Não tenho certeza se isso se aplica a você, já que geralmente uso o WPF, mas vejo você
ViewModel
fazendo referência a isso.Em vez de desativar o botão, defina um
IsLoading
sinalizador paratrue
. Qualquer elemento da interface do usuário que você deseja desativar pode ser vinculado ao sinalizador. O resultado final é que se torna tarefa da interface do usuário classificar seu próprio estado ativado, não sua lógica de negócios.Além disso, a maioria dos botões no WPF está vinculada a um
ICommand
e, geralmente, eu defino oICommand.CanExecute
igual a!IsLoading
, portanto, impede automaticamente a execução do comando quando IsLoading é igual a true (também desativa o botão para mim)fonte
Não há nada de frágil nisso, e é o que o usuário esperaria. Se o usuário precisar esperar antes de pressionar o botão novamente, faça-o esperar.
Posso ver como certos cenários de controle podem tornar a decisão de quais controles desabilitar / habilitar a qualquer momento bastante complexa, mas é surpreendente que o gerenciamento de uma interface complexa seja complexo? Se você tiver muitos efeitos colaterais assustadores de um controle específico, sempre poderá desativar o formulário inteiro e executar um "carregamento" de um show completo sobre a coisa toda. Se esse for o caso em todos os controles, sua interface do usuário provavelmente não será muito bem projetada em primeiro lugar (acoplamento rígido, agrupamento de controle ruim etc.).
fonte
Desativar o widget é a opção menos complexa e propensa a erros. Você o desabilita no manipulador antes de iniciar a operação assíncrona e a reativa no final da operação. Portanto, o desativar + habilitar para cada controle é mantido local para a manipulação desse controle.
Você pode até criar um invólucro, que terá um delegado e controle (ou mais deles), desabilitará os controles e executará algo de forma assíncrona, que fará o delegado e depois reativará os controles. Ele deve cuidar das exceções (ative finalmente), para que ainda funcione de forma confiável se a operação falhar. E você pode combiná-lo com a adição de mensagem na barra de status, para que o usuário saiba o que está esperando.
Você pode adicionar alguma lógica para contar as desativações, para que o sistema continue funcionando se você tiver duas operações, que devem desativar uma terceira, mas que não são mutuamente exclusivas. Você desativa o controle duas vezes, para que ele seja ativado novamente apenas se você o ativar duas vezes novamente. Isso deve cobrir a maioria dos casos, mantendo os casos separados o máximo possível.
Por outro lado, qualquer coisa como máquina de estado se tornará complexa. Na minha experiência, todas as máquinas de estado que eu já vi eram difíceis de manter e sua máquina de estado precisaria abranger todo o diálogo, amarrando tudo a tudo.
fonte
Embora Jan Hudec e Rachel tenham expressado idéias relacionadas, eu sugeriria usar algo como uma arquitetura Model-View - expressar o estado do formulário em um objeto e vincular os elementos do formulário a esse estado. Isso desabilitaria o botão de uma maneira que pudesse ser dimensionada para cenários mais complexos.
fonte