Soluções para reentrada assíncrona em C # 5


16

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 awaitchamada 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?


O que é frágil nisso? Dá ao usuário uma indicação de que algo está sendo processado. Adicione algumas indicações dinâmicas de trabalho que estão sendo processadas e isso parece perfeitamente uma boa interface do usuário para mim.
Steven Jeuris 10/10

PS: O C # do .NET 4.5 é chamado C # 5?
Steven Jeuris 10/10

1
Apenas curioso por que esta questão está aqui e não tão? Este é definitivamente sobre o código para que ele iria estar ali eu acho ...
C. Ross

@ C.Ross, isso é mais uma questão de design / interface do usuário. Não há realmente um ponto técnico que precise ser explicado aqui.
Morgan Herlocker 10/10

Temos um problema semelhante nos formulários HTML do ajax, nos quais um usuário pressiona o botão de envio, uma solicitação de ajax é enviada e, em seguida, queremos impedir o usuário de enviar novamente, desabilitar o botão é uma solução muito comum nessa situação
Raynos

Respostas:


14

Então, algo está me incomodando sobre o novo suporte assíncrono no 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?

Vamos começar observando que isso é 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ê 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, awaitao contrário, DoEventsé que awaitaumenta a probabilidade de novas tarefas não "morrerem de fome" nas tarefas atualmente em execução. DoEventsbombeia o loop de mensagem e, em seguida, chama de forma síncrona o manipulador de eventos reentrante, enquanto awaitnormalmente enfileira a nova tarefa para que ela seja executada em algum momento no futuro.

Nas demos que eu vi, elas desativam o botão antes da chamada em espera e ativam-no novamente depois. 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. 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?

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.

Estou tentado a mostrar apenas uma caixa de diálogo modal durante a operação, mas parece um pouco como usar uma marreta.

Os usuários vão odiar isso.


Obrigado Eric, acho que essa é a resposta definitiva - depende do desenvolvedor do aplicativo.
Nicholas Butler

6

Por que você acha que desativar o botão antes do awaite 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.


Claro que você tem a erros punho - Eu atualizei a minha pergunta
Nicholas Butler

5

Não tenho certeza se isso se aplica a você, já que geralmente uso o WPF, mas vejo você ViewModelfazendo referência a isso.

Em vez de desativar o botão, defina um IsLoadingsinalizador para true. 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 ICommande, geralmente, eu defino o ICommand.CanExecuteigual a !IsLoading, portanto, impede automaticamente a execução do comando quando IsLoading é igual a true (também desativa o botão para mim)


3

Nas demos que eu já vi, elas desativam o botão antes da chamada em espera e o ativam novamente depois. Isso me parece uma solução muito frágil em um aplicativo do mundo real.

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


Obrigado - mas como gerenciar a complexidade?
Nicholas Butler

Uma opção seria colocar controles relacionados no contêiner. Desativar / ativar pelo contêiner de controle, não pelo controle. ou seja: EditorControls.Foreach (c => c.Enabled = false);
Morgan Herlocker 10/10

2

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.


Obrigado - eu gosto dessa ideia. Então, você teria um objeto gerenciador para sua janela que conte o número de solicitações de desativação e ativação de cada controle?
Nicholas Butler

@NickButler: Bem, você já tem um objeto gerenciador para cada janela - o formulário. Então, eu teria uma classe implementando as solicitações e contando e apenas adicionaria instâncias aos formulários relevantes.
Jan Hudec 10/10

1

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.