在多种情况下报告进度:控制台应用程序,Windows窗体。
上下文:
我们有一些工作流程,该流程将查找要处理的数据并对其进行处理。 在Task Scheduler或Windows Service中使用它们。调用Run()不会得到任何回报。这是从邮件,数据库等处获取数据的过程的基本示例。做点什么。保存已处理。失败时发出警报。
但是对于Gui的Console,我们将需要Run进程来反馈其进度。
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;
}
}
对于Gui,我将添加一名后台工作人员ReportProgress()
。
对于控制台,我将添加以下内容:
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]");
我想装饰我的接口和类,这样我就可以轻松地报告任何进度。无需修改Run to mutch。
答案 0 :(得分:1)
让我们考虑一个长时间运行的操作,该操作处理一堆图像。
foreach(Image image in myImages) {
processImage(image);
}
要使此代码具有报告进度的功能,我们将与进度对象进行通信并定期进行报告。已经有许多设计决策。也许最重要的是:我怎么知道要提前完成多少工作?如果我知道,那么我可以先报告有多少个项目,然后每次要做某事时,我可以说其中一项是完整的。或者,我可以只报告一个百分比。通常,进度接口会执行前者。因此,我们至少需要修改代码以报告进度,就像这样。
progress.ExpectSteps(myImages.Length);
foreach(Image image in myImages) {
processImage(image);
progress.CompleteStep();
}
在这里,根据这个最小接口(我自己设计的),我将其分为两个呼叫。
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();
}
调用函数时,将向其传递一个抽象化报告进度行为的对象。
void ProcessImages(IProgress progress) {
progress.ExpectSteps(myImages.Length);
foreach(Image image in myImages) {
processImage(image);
progress.CompleteStep();
}
}
现在要实现提供IProgress接口的对象,我们需要做出另一个决定:哪个线程将调用方法?如果您完全控制线程,那么您可能知道它将永远是调用接口的辅助线程。然后,该实现将必须调用或 dispatch 返回用户界面线程的方法,该方法将向用户显示进度摘要。如果要使代码可以在主线程上运行更加笼统,那么您可能必须精明并利用InvokeRequired之类的功能。
但基本上,根据调用该方法的接口,它可以提供实现IProgress
并调用ProcessImages
函数的对象。
这是一个不支持线程的控制台应用程序的示例实现,其中所有工作都在主线程上完成。
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);
}
}
这是不支持多线程的实现,并且是自定义Windows窗体对话框。
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));
}