Meilleure stratégie pour signaler les progrès à l'interface utilisateur - comment le rappel devrait-il se produire?


11

Parfois, l'utilisateur démarre une opération technique étendue qui prend un certain temps à exécuter. Dans ces cas, il est généralement agréable d'afficher une sorte de barre de progression, ainsi que des informations sur la tâche en cours d'exécution.

Afin d'éviter de coupler étroitement l'interface utilisateur et les couches logiques, il est généralement préférable que la communication se fasse via une sorte de proxy. Autrement dit, le back-end ne doit pas manipuler ses propres éléments d'interface utilisateur, ni même interagir directement avec la couche intermédiaire.

De toute évidence, il doit y avoir un rappel quelque part pour que cela fonctionne. Je l'ai généralement implémenté de deux manières:

  1. Passez un objet modifiable au back-end et demandez-lui de le modifier au fur et à mesure. L'objet avertit le serveur frontal lorsqu'un changement se produit.

  2. Passez une fonction de rappel du formulaire void f(ProgressObject)ou ProgressObject -> unitque le back-end invoque. Dans ce cas, le back-end construit le ProgressObjectet il est complètement passif. Il doit construire un nouvel objet à chaque fois qu'il veut rendre compte des progrès, je suppose.

Quels sont les inconvénients et les avantages de ces méthodes? Existe-t-il une meilleure méthode convenue à utiliser? Y a-t-il des circonstances différentes pour leur utilisation?

Y a-t-il des techniques complètement différentes pour signaler les progrès que j'ai ignorées?


1
Concernant mutable vs immuable, les avantages et les inconvénients sont les mêmes que partout ailleurs. En ce qui concerne l'objet progress, cela peut être très léger; cela peut être aussi simple qu'un seul chiffre: un pourcentage.
Robert Harvey

@RobertHarvey La taille de l'objet progress dépend généralement des exigences de l'interface utilisateur. Regardez la boîte de dialogue de copie de Windows par exemple. J'imagine que cela nécessite beaucoup d'informations.
GregRos

1
@RobertHarvey C'est nouveau pour moi. Qu'Est-ce que c'est?
GregRos

1
Je vais mordre. Nous utilisons BackgroundWorkerce RH mentionne. Enveloppé dans une classe personnalisée avec un "formulaire de progression", etc. et un mécanisme simple pour communiquer une exception - comme BackgroundWorkerpar conception s'exécute dans un thread séparé. Dans la mesure où nous utilisons ses fonctionnalités d'une manière suggérée par .Net, cela pourrait être considéré comme idiomatique. Et dans n'importe quel contexte de langage / cadre donné, "idiomatique" peut être le meilleur.
radarbob

2
Je ne vois aucune différence significative entre vos deux méthodes. Un objet passé du frontal au backend qui propose des méthodes qui conduisent à une notification du frontal a en fait la fonction d'un rappel. Et si votre deuxième approche utilise un objet paramètre plus ou moins complexe pour transmettre des informations, ou si elle utilise quelques valeurs simples, cela ne fait aucune différence d'un point de vue architectural. Dans les deux approches, le backend informe activement le frontend, les différences ne sont que des détails mineurs, il n'y a donc pas de concept différent décrit ici.
Doc Brown

Réponses:


8

Passez un objet modifiable au back-end et demandez-lui de le modifier au fur et à mesure. L'objet avertit le serveur frontal lorsqu'un changement se produit.

Il est difficile d'équilibrer l'efficacité si le back-end le notifie à cet égard. Sans précaution, vous pourriez constater que l'augmentation de votre progression finit par doubler ou tripler le temps nécessaire pour terminer l'opération si vous visez une mise à jour de progression très fluide.

Passez une fonction de rappel de la forme void f (ProgressObject) ou ProgressObject -> unité que le back-end invoque. Dans ce cas, le back-end construit le ProgressObject et il est complètement passif. Il doit construire un nouvel objet à chaque fois qu'il veut rendre compte des progrès, je suppose.

Je ne comprends pas tellement la différence ici.

Y a-t-il des techniques complètement différentes pour signaler les progrès que j'ai ignorées?

Sondage depuis le front-end dans un thread séparé avec des incréments atomiques dans le backend. L'interrogation est logique ici, car il s'agit d'une opération qui se termine dans une période finie et la probabilité que le frontend détecte des changements d'état est élevée, surtout si vous visez une barre de progression lisse et soyeuse. Vous pouvez envisager des variables de condition si vous n'aimez pas l'idée d'interroger à partir du thread frontal, mais dans ce cas, vous voudrez peut-être éviter de notifier chaque incrément de barre de progression granulaire.


2

C'est la différence entre un mécanisme de notification push et pull .

L'objet mutable (l' extraction ) devra être interrogé de manière répétée par l'interface utilisateur et synchronisé si vous vous attendez à ce que la tâche principale soit exécutée dans un thread d'arrière-plan / de travail.

Le rappel (le push ) ne créera du travail pour l'interface utilisateur que lorsque quelque chose change réellement. De nombreux frameworks d'interface utilisateur ont également un appel invokeOnUIThread à partir d'un thread de travail pour faire exécuter un morceau de code sur le thread d'interface utilisateur afin que vous puissiez réellement apporter les modifications sans entrer dans les dangers liés au thread. (jeu de mots volontaire)

En général, les notifications push sont préférables car elles ne fonctionnent que lorsque le travail doit être fait.


1
Je pense que ce que vous dites est juste en général. Cependant, pour ce cas particulier, une barre de progression, des changements peuvent se produire rapidement. Si vous vous attendez à ce que le «progrès» change probablement plusieurs fois par seconde, il est plus logique d'utiliser le modèle d'extraction, sinon vous devez vous inquiéter de voir l'interface utilisateur recevoir trop de notifications à gérer.
Gort the Robot

L'envoi d'un objet de progression peut masquer le mécanisme de notification que vous utilisez depuis le back-end, car l'objet de progression effectue peut-être les rappels. En fait, je n'ai jamais utilisé de mécanisme de traction pour autant que je m'en souvienne, et j'ai en quelque sorte oublié: 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.- Pas si l'objet modifiable est la boîte de dialogue elle-même ou une interface de travail avec elle. Bien sûr, cela équivaut à un rappel de toute façon.
Robert Harvey

1
Hein? L'OP décrit clairement deux formes différentes d'un mécanisme de poussée, dans aucun cas une interrogation n'est nécessaire.
Doc Brown du

0

J'utilise des websockets avec AngularJS. Lorsque le frontal reçoit un message, il l'affiche dans une zone de message désignée qui s'estompe pour s'éteindre après quelques secondes. À l'arrière, je poste simplement des messages d'état dans une file d'attente de messages. J'envoie uniquement du texte, mais il n'y a aucune raison pour laquelle je n'ai pas pu envoyer d'objet d'état avec des valeurs telles que le pourcentage d'achèvement ou la vitesse de transfert.


0

Vous mentionnez vos «deux façons» comme s'il s'agissait de concepts distincts, mais je veux y revenir un peu.

  1. Passez un objet modifiable au back-end et demandez-lui de le modifier au fur et à mesure. L'objet avertit le serveur frontal lorsqu'un changement se produit.

Vous avez déjà dit que vous vouliez éviter de coupler étroitement l'interface utilisateur et la logique, je peux donc supposer en toute sécurité que cet "objet mutable" que vous passez est en fait une implémentation d'une interface particulière définie dans le module logique. En tant que tel, c'est simplement une autre façon de passer un rappel dans le processus qui est périodiquement appelé avec des informations sur les progrès.

Quant aux avantages et inconvénients ...

Un inconvénient de la méthode (1) est que la classe qui implémente l'interface ne peut le faire qu'une seule fois. (Si vous souhaitez effectuer différents travaux avec différentes invocations, vous aurez besoin d'une instruction switch ou du modèle de visiteur.) Avec la méthode (2), le même objet peut utiliser un rappel différent pour chaque invocation du code backend sans avoir besoin d'un commutateur.

La force de la méthode (1) est qu'il est beaucoup plus facile d'avoir plusieurs méthodes sur l'interface que de traiter les rappels multiples de la méthode (2) ou un seul rappel avec une instruction switch pour plusieurs contextes.


-2

Les techniques que vous pouvez utiliser peuvent être très différentes.

J'essaie de comprendre avec un scénario différent

  • Demande à db
  • télécharger un fichier

Une simple demande de connexion à db (signifie répondre à partir de db avec un elemt) n'a pas besoin d'un rapport de progression mais il peut toujours déclencher le thread d'interface utilisateur dans une tâche séparée ex. async ou backgroundworker, ici vous avez juste besoin d'un rappel pour le résultat.

Mais que se passe-t-il si vous demandez à voir tout votre inventaire avec un article de 1 mln? Cette requête devrait prendre plusieurs minutes, donc dans ce cas, vous devez implémenter la progression de votre port dans votre logique métier sous la forme élément / éléments, puis vous pouvez mettre à jour votre interface utilisateur et vous pouvez donner l'option annuler le rappel.

Même cas pour le téléchargement de fichiers. Vous pouvez toujours implémenter ici votre rappel de progression sous la forme d'octets d'octets, et maintenir tout le contrôle de la communication sur http est très souvent un modèle.

Dans mon approche personnelle, j'implémente ma logique de progression commerciale uniquement à mes clients, en évitant de partager d'autres objets avec le point final.


1
Cela ne répond pas vraiment à la question des avantages / inconvénients.
Benni
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.