Question

Sometimes the user starts an extended technical operation that takes a while to execute. In these cases, it's usually nice to display some kind of progress bar, along with information about which task is being executed right now.

In order to avoid close-coupling the UI and the logic layers, it's usually best to have the communication occur via some kind of proxy. That is, the back-end should not be manipulating its own UI elements, or even interacting with the intermediary layer directly.

Obviously, there has to be some call-back somewhere to make this work. I've generally implemented it in one of two ways:

  1. Pass a mutable object to the back-end, and have the back-end make changes to it on progress. The object notifies the front-end when a change occurs.

  2. Pass a call-back function of the form void f(ProgressObject) or ProgressObject -> unit that the back-end invokes. In this case, the back-end constructs the ProgressObject and it's completely passive. It must construct a new object every time it wants to report progress, I assume.

What are the drawbacks and advantages of these methods? Is there an agreed-upon best method to use? Are there different circumstances for their use?

Are there completely different techniques of reporting progress that I've overlooked?

Était-ce utile?

La solution

Pass a mutable object to the back-end, and have the back-end make changes to it on progress. The object notifies the front-end when a change occurs.

It's difficult to balance efficiency if the backend notifies in this respect. Without care you might find that incrementing your progress ends up doubling or tripling the time it takes to complete the operation if you're aiming for a very smooth progress update.

Pass a call-back function of the form void f(ProgressObject) or ProgressObject -> unit that the back-end invokes. In this case, the back-end constructs the ProgressObject and it's completely passive. It must construct a new object every time it wants to report progress, I assume.

I don't get the difference here so much.

Are there completely different techniques of reporting progress that I've overlooked?

Poll from the front-end in a separate thread with atomic increments in the backend. Polling makes sense here since it's for an operation that finishes in a finite period and the likelihood of the frontend picking up state changes is high, especially if you're aiming for a silky smooth progress bar. You could consider condition variables if you don't like the idea of polling from the frontend thread, but in that case you might want to avoid notifying on every single granular progress bar increment.

Autres conseils

This is the difference between a push and pull notification mechanism.

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.

The callback (the push) will only create work for the UI when something actually changes. Many UI frameworks also have a invokeOnUIThread callable from a worker thread to make a piece of code run on the UI thread so you can actually make the changes without treading in to thread-related hazards. (pun intended)

In general push notifications are preferable because they only make work when work needs to be done.

I'm using websockets with AngularJS. When the front end receives a message it displays it in a designated message area which fades to blank after a few seconds. On the back end I just post status messages to a message queue. I only send text, but there's no reason I couldn't send a status object with values like percentage complete or transfer speed.

You mention your "two ways" as if they are separate concepts but I want to push back on that a bit.

  1. Pass a mutable object to the back-end, and have the back-end make changes to it on progress. The object notifies the front-end when a change occurs.

You have already said that you want to avoid close-coupling the UI and the logic, so I can safely assume that this "mutable object" that you are passing is actually an implementation of a particular interface which is defined in the logic module. As such, this is merely another way of passing a callback into the process which is periodically calls with information about progress.

As for benefits and drawbacks...

A drawback for method (1) is that the class that implements the interface can only do it once. (If you want to perform different jobs with different invocations, you will need a switch statement or the visitor pattern.) With method (2), the same object can use a different callback for every invocation of the backend code without the need for a switch.

A strength for method (1) is that it is far easier to have multiple methods on the interface than to deal with method (2)'s multiple callbacks or single callback with a switch statement for multiple contexts.

The techniques you can use may be very differt.

I try to figure out with different scenario

  • Request to db
  • download file

A simple login request to db (mean respond from db with one elemt) don't need a report progress but it alwasy can fire off UI thread in separate task ex. async or backgroundworker, here you just need one callback for result.

But what if you query for see all your inventory with 1mln item? This query should take several minutes to complete, so in this case you need to implement your perport progress in your business logic in form item / items, then you can update your UI and may be give the option cancel callback.

Same case for file download. You can always implement here your progress callback in form byte of bytes, and hold all comunication control over http is very commonly pattern.

On my personally approach i implement my business progress logic only to my clients, avoiding to share other object with end-point.

Licencié sous: CC-BY-SA avec attribution
scroll top