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.
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 ...
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.
In the process of creating the library, I came across a couple of "messy" issues. Here's how I solved them:
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.
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.
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.
Here are some of the particularly cool features:
for
loop with 20 iterations worth 5% eachIEnumerable<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();
}
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With