Reputation: 2057
Reporting progression, in multiple context: Console apps, windows form.
Context:
We have some working process, that will look for data to be handle and handle them. Using them in Task Scheduler or Windows Service. Call the Run() expect nothing in return. This is a basic exemple of a process Getting data from somewhere mail, db, etc. . Do something. Save the processed. Alert when failed.
But for Gui, Console, We will need the Run process to give use a feed back on its progress.
public interface IProcess
{
bool Init(out string error);
bool Init(Configuration config, out string error);
bool Run(out ErrorCode errorCode);
bool Run(object Item, out ErrorCode errorCode);
bool IsInitialized { get; set; }
bool IsDebugMode { get; set; }
}
public class SO_ExempleProcess : IProcess
{
public bool Run(out ErrorCode errorCode)
{
errorCode = new ErrorCode(1, "");
var items = db.Select(x=> );
if (!items.Any())
{
Logger.Debug("No data to integrate. ");
errorCode = new ErrorCode(2, "");
return true;
}
foreach (var item in items)
{
var foo = SubStep(item);
var isGood = MainStep(foo, item);
if (!isGood)
{
Logger.Alert("Error processing item:", item);
continue;
}
item.ProcessTicket = true;
item.ProcessBy = "From, API key, Instance(Scheduler, Service, Gui, Console, Debug)";
item.ProcessDate = DateTime.Now;
}
db.SaveChanges();
return true;
}
}
For Gui I will add a background worker ReportProgress()
.
For Console I will add something like :
decimal total = items.Count(); int last = 0; int index = 0;
Console.WriteLine($"> SO process name ");
Console.WriteLine("[InProgress]");
Console.Write(">");
foreach (var item in items)
{
//process
var percent = (index / total) * 100;
if ((percent / 10) > last)
{
var _new = decimal.ToInt32(percent / 10);
Console.Write(new String('#', _new - last));
last = _new;
}
}
Console.WriteLine("\n> 100%");
Console.WriteLine("[Completed]");
I would like to decorate my Interface and class so I will be able to easly to report any progression. Without having to modify the Run to mutch.
Upvotes: 0
Views: 98
Reputation: 11750
Let's consider a long running operation that processes a bunch of images.
foreach(Image image in myImages) {
processImage(image);
}
To give this code the ability to report progress, we'll communicate with a progress object and report at intervals. There are already a number of design decisions to be made. Perhaps the most important is: how do I know how much work there is to be done ahead of time? If I know, then I can begin by reporting how many items there are, and then each time there is something to be done, I can say that one of those items is complete. Alternatively, I can just report a percentage. Typically, though, progress interfaces do the former. So we'll at least need to modify our code to report progress like this.
progress.ExpectSteps(myImages.Length);
foreach(Image image in myImages) {
processImage(image);
progress.CompleteStep();
}
Here I've broken it into two calls according to this minimal interface (of my own design).
public interface IProgress
{
/// <summary>
/// Call this first to indicate how many units of work are expected to be completed.
/// </summary>
/// <param name="numberOfStepsToExpect">The number of units of work to expect.</param>
void ExpectSteps(int numberOfStepsToExpect);
/// <summary>
/// Call this each time you complete a unit of work.
/// </summary>
void CompleteStep();
}
When we call our function, we will pass it an object that abstracts away the act of reporting progress.
void ProcessImages(IProgress progress) {
progress.ExpectSteps(myImages.Length);
foreach(Image image in myImages) {
processImage(image);
progress.CompleteStep();
}
}
Now to implement an object that provides an IProgress interface we need to make another decision: Which thread will be calling the methods? If you have full control over the threading, then you may know that it will always be a worker thread calling the interfaces. The implementation will then have to invoke or dispatch a method back to the user interface thread that will display a summary of progress to the user. If you want to make it more general that code could be running on the main thread, then you may have to be smart and make use of features like InvokeRequired.
But basically, depending on which interface is calling the method, it can provide an object that implements IProgress
and call the ProcessImages
function.
Here's an example implementation for a Console application that does NOT support threading, where all work is done on the main thread.
internal class ProgressReport : IProgress
{
int numberOfStepsToExpect = 100;
int numberOfStepsCompleted = 0;
void IProgress.ExpectSteps(int numberOfStepsToExpect)
{
this.numberOfStepsToExpect = numberOfStepsToExpect;
}
void IProgress.CompleteStep()
{
++numberOfStepsCompleted;
ReportProgress();
}
private void ReportProgress()
{
Console.WriteLine("{0}% complete", 100 * numberOfStepsCompleted / numberOfStepsToExpect);
}
}
Here's an implementation that does support multithreading, and is a custom Windows Form dialog.
int numberOfStepsCompleted = 0;
int numberOfStepsToExpect = 100;
void IProgress.CompleteStep()
{
if (this.InvokeRequired)
{
Invoke((Action) delegate { ((IProgress)this).CompleteStep(); });
return;
}
++numberOfStepsCompleted;
}
void IProgress.ExpectSteps(int numberOfStepsToExpect)
{
if (this.InvokeRequired)
{
Invoke((Action)delegate { ((IProgress)this).ExpectSteps(numberOfStepsToExpect); });
return;
}
this.numberOfStepsToExpect = numberOfStepsToExpect;
ReportProgress();
}
void ReportProgress()
{
this.label1.Text = string.Format("{0}% complete", 100 * numberOfStepsCompleted / numberOfStepsToExpect));
}
Upvotes: 1