Melhor estratégia para relatar o progresso para a interface do usuário - como deve ocorrer o retorno de chamada?

11

Às vezes, o usuário inicia uma operação técnica estendida que demora um pouco para ser executada. Nesses casos, geralmente é bom exibir algum tipo de barra de progresso, juntamente com informações sobre qual tarefa está sendo executada no momento.

Para evitar o acoplamento próximo entre a interface do usuário e as camadas lógicas, geralmente é melhor que a comunicação ocorra por meio de algum tipo de proxy. Ou seja, o back-end não deve manipular seus próprios elementos de interface do usuário nem interagir diretamente com a camada intermediária.

Obviamente, deve haver algum retorno de chamada em algum lugar para que isso funcione. Geralmente, eu o implementei de duas maneiras:

  1. Passe um objeto mutável para o back-end e faça com que o back-end faça alterações no andamento. O objeto notifica o front-end quando ocorre uma alteração.

  2. Passe uma função de retorno de chamada no formulário void f(ProgressObject)ou ProgressObject -> unitque o back-end chama. Nesse caso, o back-end constrói o ProgressObjecte é completamente passivo. Ele deve construir um novo objeto toda vez que desejar relatar progresso, presumo.

Quais são as desvantagens e vantagens desses métodos? Existe um melhor método acordado para usar? Existem circunstâncias diferentes para o seu uso?

Existem técnicas completamente diferentes de relatar o progresso que eu negligenciei?

GregRos
fonte
1
Em relação a mutável versus imutável, as vantagens e desvantagens são as mesmas de qualquer outro lugar. Em relação ao objeto de progresso, isso pode ser muito leve; pode ser tão simples quanto um único número: uma porcentagem.
Robert Harvey
@RobertHarvey O tamanho do objeto de progresso geralmente depende dos requisitos da interface do usuário. Veja a caixa de diálogo de cópia do Windows, por exemplo. Eu imagino que isso requer muita informação.
GregRos
1
@RobertHarvey Isso é novidade para mim. O que é isso?
GregRos
1
Eu vou morder. Nós usamos BackgroundWorkeresse RH menciona. Embrulhado em uma classe personalizada, juntamente com um "formulário de progresso" etc., e um mecanismo simples para comunicar uma exceção - conforme o BackgroundWorkerdesign é executado em um encadeamento separado. Na medida em que usamos seus recursos da maneira sugerida pelo .Net, isso pode ser considerado idiomático. E em qualquer contexto de linguagem / estrutura, "idiomático" pode ser o melhor.
Radarbob
2
Não vejo diferenças significativas entre seus dois métodos. Um objeto passado do front-end para o back-end, que oferece métodos que levam a uma notificação do front-end, tem realmente a função de um retorno de chamada. E se sua segunda abordagem usar um objeto de parâmetro mais ou menos complexo para transmitir informações ou se usar alguns valores simples, isso não fará diferença do ponto de vista arquitetural. Nas duas abordagens, o back-end informa ativamente o front-end; as diferenças são apenas pequenos detalhes; portanto, não há um conceito diferente descrito aqui.
Doc Brown

Respostas:

8

Passe um objeto mutável para o back-end e faça com que o back-end faça alterações no andamento. O objeto notifica o front-end quando ocorre uma alteração.

É difícil equilibrar a eficiência se o back-end notificar a esse respeito. Sem cuidado, você pode descobrir que incrementar seu progresso acaba dobrando ou triplicando o tempo necessário para concluir a operação, se você estiver buscando uma atualização de progresso muito suave.

Passe uma função de retorno de chamada no formulário void f (ProgressObject) ou ProgressObject -> unidade que o back-end chama. Nesse caso, o back-end cria o ProgressObject e é completamente passivo. Ele deve construir um novo objeto toda vez que desejar relatar progresso, presumo.

Eu não entendo muito a diferença aqui.

Existem técnicas completamente diferentes de relatar o progresso que eu negligenciei?

Faça uma pesquisa a partir do front-end em um thread separado com incrementos atômicos no back-end. A pesquisa faz sentido aqui, pois é para uma operação que termina em um período finito e a probabilidade de o front-end pegar mudanças de estado é alta, especialmente se você está buscando uma barra de progresso suave e sedosa. Você pode considerar variáveis ​​de condição se não gostar da idéia de pesquisar a partir do encadeamento de front-end, mas nesse caso, convém evitar notificar cada incremento de barra de progresso granular.


fonte
2

Essa é a diferença entre um mecanismo de notificação push e pull .

O objeto mutável (o pull ) precisará ser pesquisado repetidamente pela interface do usuário e sincronizado se você espera que a tarefa de back-end seja executada em um thread de segundo plano / trabalhador.

O retorno de chamada ( push ) só criará trabalho para a interface do usuário quando algo realmente mudar. Muitas estruturas de interface do usuário também têm um invokeOnUIThread chamado de um thread de trabalho para fazer com que um pedaço de código seja executado no thread da interface do usuário, para que você possa realmente fazer as alterações sem abordar os riscos relacionados ao thread. (trocadilho pretendido)

Em geral, as notificações por push são preferíveis porque só funcionam quando o trabalho precisa ser feito.

catraca arrepiante
fonte
1
Eu acho que o que você diz é certo em geral. No entanto, nesse caso em particular, uma barra de progresso, as alterações podem ocorrer rapidamente. Se sua expectativa é de que o "progresso" provavelmente mude muitas vezes por segundo, faz mais sentido usar o modelo pull, caso contrário, você precisa se preocupar com a interface do usuário que recebe muitas notificações para lidar.
Gort the Robot
O envio de um objeto de progresso pode ocultar o mecanismo de notificação que você está usando no back-end, pois talvez o objeto de progresso esteja fazendo as chamadas de retorno. Na verdade, nunca usei um mecanismo de tração, pelo que me lembro, e meio que me esqueci dele: P
GregRos
The mutable object (the pull) will need to be repeatably polled by the UI and synchronized if you expect the back-end task to be executed in a background/worker thread.- Não se o objeto mutável for o próprio diálogo ou uma interface de trabalho para ele. Obviamente, isso equivale a um retorno de chamada.
Robert Harvey
1
Hã? O OP descreve claramente duas formas diferentes de um mecanismo push, em nenhuma é necessária uma pesquisa.
Doc Brown
0

Estou usando websockets com AngularJS. Quando o front-end recebe uma mensagem, ele é exibido em uma área de mensagem designada que desaparece após alguns segundos. No back-end, eu apenas posto mensagens de status em uma fila de mensagens. Só envio texto, mas não há motivo para não enviar um objeto de status com valores como porcentagem concluída ou velocidade de transferência.

TMN
fonte
0

Você menciona seus "dois caminhos" como se fossem conceitos separados, mas quero insistir um pouco nisso.

  1. Passe um objeto mutável para o back-end e faça com que o back-end faça alterações no andamento. O objeto notifica o front-end quando ocorre uma alteração.

Você já disse que deseja evitar o acoplamento próximo da interface do usuário e da lógica, para que eu possa assumir com segurança que esse "objeto mutável" que você está passando é na verdade uma implementação de uma interface específica definida no módulo lógico. Como tal, essa é apenas outra maneira de transmitir um retorno de chamada para o processo, que periodicamente é chamado com informações sobre o progresso.

Quanto aos benefícios e desvantagens ...

Uma desvantagem do método (1) é que a classe que implementa a interface pode fazê-lo apenas uma vez. (Se você deseja executar tarefas diferentes com chamadas diferentes, precisará de uma instrução switch ou do padrão de visitante.) Com o método (2), o mesmo objeto pode usar um retorno de chamada diferente para cada chamada do código de back-end sem a necessidade de um interruptor.

O ponto forte do método (1) é que é muito mais fácil ter vários métodos na interface do que lidar com os vários retornos de chamada do método (2) ou o retorno de chamada único com uma instrução switch para vários contextos.

Daniel T.
fonte
-2

As técnicas que você pode usar podem ser muito diferentes.

Eu tento descobrir com cenário diferente

  • Pedido para db
  • ⇬ Fazer download do arquivo

Uma simples solicitação de login para o banco de dados (resposta média do banco de dados com um elemt) não precisa de um progresso no relatório, mas também pode disparar o thread da interface do usuário em tarefas separadas, por exemplo. assíncrono ou trabalhador em segundo plano, aqui você só precisa de um retorno de chamada para obter o resultado.

Mas e se você consultar para ver todo o seu inventário com o item 1mln? Essa consulta deve levar alguns minutos para ser concluída. Nesse caso, você precisa implementar o progresso do seu perport na lógica de negócios nos itens / itens do formulário, para atualizar sua interface do usuário e ter a opção de cancelar o retorno de chamada.

Mesmo caso para download de arquivo. Você sempre pode implementar aqui seu retorno de chamada de progresso no formato byte de bytes e manter todo o controle de comunicação sobre http é muito comum.

Na minha abordagem pessoal, implementei minha lógica de progresso de negócios apenas para meus clientes, evitando compartilhar outro objeto com o ponto final.

Marco Luongo
fonte
1
Isso realmente não responde à pergunta sobre vantagens / desvantagens.
Benni