Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's a good way to report progress from a Repository to the UI

I'm looking for a good way, or at least some insight, on how to cleanly report back progress to the UI layer.

My situation is as follows: I got a Repository in the Infrastructure Layer which communicates with the Data Layer. While those two are working the UI has currently no clue on what is going on and only passively waits for results (using an Observable)

My first thoughts were to expose events in the Repository to which the UI binds. But I feel that this can get quite messy. I would love to hear how you guys have solved this.

like image 852
ndsc Avatar asked Dec 31 '11 15:12

ndsc


People also ask

How do I write a monthly progress report?

Be concise. Avoid a report that is too structured (like reporting by each WP) Do not keep reporting past issues by adding new things to old ones which are not relevant anymore for the time span covered in the report. Consider the Monthly Progress Report as a tool not only to keep ESA informed, but the whole project ...


2 Answers

I've done a lot of work with background processing and reporting progress, so I completely understand how "messy" it can get.

I created an open-source library that really provides a good framework for reporting progress, especially in "nested" scenarios! Check out Progression.

I'd recommend taking a look at some of the unit tests in the project, because there's plenty of usage examples there. There's also a runnable demo to see the code in action.

General Concepts

In the process of creating the library, I came across a couple of "messy" issues. Here's how I solved them:

  1. Reporting progress from multiple layers
    At one time, I was passing a common "progress" object into each layer, so that each layer could report to it. This approach sucked. Every method had to pass the object along, even if it didn't use it.
    Instead, I decided to use a static class Progress that acts like a singleton. This way, each layer directly reports progress (if it wants).
    The progress can be monitored by attaching to an event, or by polling.
    This approach makes it VERY easy to use Progression to report and to read progress.

  2. Thread Safety
    Calculating progress is usually only necessary when you have a UI that shows the progress of a background task, right? So obviously, I had to really consider thread-safety.
    However, I really want to avoid using typical locking mechanisms, because they're costly and I definitely didn't want to slow down the already-slow background tasks. So, my solution was to use the background task to create a standalone ProgressChangedInfo object, which can be safely passed between threads.

  3. Cross Threading
    Initially, my background process reported thousands of progress events, and invoked a UI update for each one. The UI would stall, trying to catch up with all the updates.
    So I introduced "polling", but I found that "important" progress, such as "copying file 2 of 3", were getting dropped in favor of less important updates, like "file 5% copied".
    Now, "important" updates will always be favored over less important updates, so the UI can attempt to update as quickly as possible, the most informative messages are always shown, and the UI will always remain completely responsive.

Progression specific information

  • A Task represents a procedure with several Steps. When a step is completed, the Task reports the progress.
  • Individual methods are responsible for starting a Task, completing Steps, and ending the Task
  • When a nested task reports progress, it is calculated appropriately towards the overall progress. There is no limit to the number of nested tasks.
  • There are 2 main ways to monitor progress:
    • You can attach to an event
    • You can "poll" the progress

Here are some of the particularly cool features:

  • There are several clever ways to calculate progress:
    • An evenly-weighted number of steps; for example, a for loop with 20 iterations worth 5% each
    • A weighted number of steps; for example, 3 steps that take 10%, 10%, 80%
    • An unknown number of steps is supported! For example, reading rows from a database. In this scenario, you provide an "estimate", and as each row is read, progress will advance, but will never reach 100% until it is complete.
  • Progress calculation is completely LAZY - so if you implement progress calculation at a low level, but there are no "listeners", then progress will be completely skipped
  • Several thread-safe features are built-in, so polling and event handling can be done by a foreground thread
  • Several extension methods are available that make the code EXTREMELY easy to use ... for example, IEnumerable<T>.WithProgress() will report all progress while iterating!

Here's some code-in-action:

public void PerformSomeTask() {
    // This task has 2 steps; the first step takes about 20%, and the second takes 80%:
    Progress.BeginWeightedTask(20, 80);
    
    // Start the first step (20%):
    Progress.NextStep();
    TaskA();
    
    // Start the second step (80%):
    Progress.NextStep();
    TaskB();
    
    // Finished!
    Progress.EndTask();     
}


public void TaskA() {
    int count = 20;
    // This task has 20 steps:
    Progress.BeginFixedTask(count);
    
    for (int i = 0; i < count; i++) {
        Progress.NextStep();
        // ...
    }
    
    Progress.EndTask();
}
public void TaskB() {
    // This task has an unknown number of steps
    // We will estimate about 20, with a confidence of .5
    Progress.BeginUnknownTask(20, .5);
    
    while (database.ReadRow()) {
        Progress.NextStep();
        // ...
    }
    Progress.EndTask();
}
like image 56
Scott Rippey Avatar answered Oct 12 '22 12:10

Scott Rippey


Uese either events or (bare) delegates.

Since you probably have multiple calls a delegate parameter might be best (not having to unregister from events).

// Repository
public delegate void ReportProgress(int percentage);

  public IEnumerable<X> GetSomeRecords(..., ReportProgress reporter)
  {
      ...;
      if (reporter != null)
          reporter(i * 100 / totalRecords);  
  }
like image 28
Henk Holterman Avatar answered Oct 12 '22 11:10

Henk Holterman