我有一个控制台应用程序,它使用类库执行一些长期运行的任务。这是一个.net核心控制台应用程序,并使用.net核心通用主机。我还使用ShellProgressBar库显示一些进度条。
我的托管服务如下
internal class MyHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private readonly IMyService _myService;
private readonly IProgress<MyCustomProgress> _progress;
private readonly IApplicationLifetime _appLifetime;
private readonly ProgressBar _progressBar;
private readonly IProgressBarFactory _progressBarFactory;
public MyHostedService(
ILogger<MyHostedService> logger,
IMyService myService,
IProgressBarFactory progressBarFactory,
IApplicationLifetime appLifetime)
{
_logger = logger;
_myService = myService;
_appLifetime = appLifetime;
_progressBarFactory = progressBarFactory;
_progressBar = _progressBarFactory.GetProgressBar(); // this just returns an instance of ShellProgressBar
_progress = new Progress<MyCustomProgress>(progress =>
{
_progressBar.Tick(progress.Current);
});
}
public void Dispose()
{
_progressBar.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_myService.RunJobs(_progress);
_appLifetime.StopApplication();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
MyCustomProgress
的位置
public class MyCustomProgress
{
public int Current {get; set;}
public int Total {get; set;}
}
和MyService
看起来像这样(Job1
,Job2
,Job3
实现IJob
)
public class MyService : IMyService
{
private void List<IJob> _jobsToRun;
public MyService()
{
_jobsToRun.Add(new Job1());
_jobsToRun.Add(new Job2());
_jobsToRun.Add(new Job3());
}
public void RunJobs(IProgress<MyCustomProgress> progress)
{
_jobsToRun.ForEach(job =>
{
job.Execute();
progress.Report(new MyCustomProgress { Current = _jobsToRun.IndexOf(job) + 1, Total = _jobsToRun.Count() });
});
}
}
IJob
是
public interface IJob
{
void Execute();
}
此设置运行良好,我可以通过创建HostedService
实例并使用一个ShellProgressBar
实例来显示IProgress
上的进度条,而我必须对其进行更新。
但是,我还有另一种IMyService
的实现,我也需要运行它,就像这样
public class MyService2 : IMyService
{
private void List<IJob> _sequentialJobsToRun;
private void List<IJob> _parallelJobsToRun;
public MyService()
{
_sequentialJobsToRun.Add(new Job1());
_sequentialJobsToRun.Add(new Job2());
_sequentialJobsToRun.Add(new Job3());
_parallelJobsToRun.Add(new Job4());
_parallelJobsToRun.Add(new Job5());
_parallelJobsToRun.Add(new Job6());
}
public void RunJobs(IProgress<MyCustomProgress> progress)
{
_sequentialJobsToRun.ForEach(job =>
{
job.Execute();
progress.Report(new MyCustomProgress { Current = _jobsToRun.IndexOf(job) + 1, Total = _jobsToRun.Count() });
});
Parallel.ForEach(_parallelJobsToRun, job =>
{
job.Execute();
// Report progress here
});
}
}
这是我一直在努力的那个。当执行_parallelJobsToRun
时,我需要能够创建一个新的子级ShellProgressBar
(ShellProgressBar.Spawn
)并将其显示为子级进度条,比如说“并行作业”。
这是我在寻求有关如何实现此目标的帮助的地方。
注意:我不想依赖包含ShellProgressBar
的类库中的MyService
非常感谢任何帮助。
答案 0 :(得分:0)
您的描述让我有些困惑,但是让我看看我是否了解您的工作。因此,如果将所有这些都包装在一个类中,那么taskList1和taskList2可能是类变量。 (通过这种方式,可以更好地命名taskList1 / 2:说出parallelTaskList等等。)然后,您可以在CheckTaskStatus()类上编写一个新方法,并迭代两个类变量。这对您有帮助还是让我完全错过了您的问题?
答案 1 :(得分:0)
您可以这样修改吗?
public Task<ICollection<IProgress<int>>> StartAsync(CancellationToken cancellationToken)
{
var progressList = _myServiceFromLibrary.RunTasks();
return Task.FromResult(progressList);
}
public ICollection<IProgress<int>> RunTasks()
{
var taskList1 = new List<ITask> { Task1, Task2 };
var plist1 = taskList1.Select(t => t.Progress).ToList();
var taskList2 = new List<ITask> { Task3, Task4, Task5 }:
var plist2 = taskList2.Select(t => t.Progress).ToList();
taskList1.foreach( task => task.Run() );
Parallel.Foreach(taskList2, task => { task.Run() });
return plist1.Concat(plist2).ToList();
}
Task.Progress
可能有进度获取器。实际上,应该通过Tasks构造函数注入IProgress。但是关键是您的公共界面不接受任务列表,因此它应该只返回进度报告的集合。
如何将进度报告者注入您的任务是另一回事,具体取决于任务的实现,并且可能不支持。开箱即用。
但是您可能应该做的是提供进度回调或进度工厂,以便创建您选择的进度报告器:
public Task StartAsync(CancellationToken cancellationToken, Action<Task,int> onprogress)
{
_myServiceFromLibrary.RunTasks(onprogress);
return Task.CompletedTask;
}
public class SimpleProgress : IProgress<int>
{
private readonly Task task;
private readonly Action<Task,int> action;
public SimpleProgress(Task task, Action<Task,int> action)
{
this.task = task;
this.action = action;
}
public void Report(int progress)
{
action(task, progress);
}
}
public ICollection<IProgress<int>> RunTasks(Action<Task,int> onprogress)
{
var taskList1 = new List<ITask> { Task1, Task2 };
taskList1.foreach(t => t.Progress = new SimpleProgress(t, onprogress));
var taskList2 = new List<ITask> { Task3, Task4, Task5 }:
taskList2.foreach(t => t.Progress = new SimpleProgress(t, onprogress));
taskList1.foreach( task => task.Run() );
Parallel.Foreach(taskList2, task => { task.Run() });
}
您可能会在这里看到,这实际上主要是关于您的任务将如何调用IProgress<T>.Report(T value)
方法的问题。
答案 2 :(得分:0)
老实说,我只会在您的任务原型中使用一个事件。
目前还不清楚您想要什么,因为您发布的代码与您在问题文本中引用的名称不匹配...拥有所有代码(例如RunTasks函数,例如IProgress)会很有帮助原型等)。
尽管如此,专门存在一个事件来通知调用代码。让我们回到基础。假设您有一个名为MyLib的库,它带有DoThings()方法。
创建一个继承自EventArgs的新类,该类将携带任务的进度报告...
public class ProgressEventArgs : EventArgs
{
private int _taskId;
private int _percent;
private string _message;
public int TaskId => _taskId;
public int Percent => _percent;
public string Message => _message;
public ProgressEventArgs(int taskId, int percent, string message)
{
_taskId = taskId;
_percent = percent;
_message = message;
}
}
然后在您的库的类定义上,添加一个事件,如下所示:
public event EventHandler<ProgressEventArgs> Progress;
然后在控制台应用程序中,为进度事件创建处理程序:
void ProgressHandler(object sender, ProgressEventArgs e)
{
// Do whatever you want with your progress report here, all your
// info is in the e variable
}
并订阅您的类库事件:
var lib = new MyLib();
lib.Progress += ProgressHandler;
lib.DoThings();
完成后,退订该事件:
lib.Progress -= ProgressHandler;
现在,在您的类库中,您可以通过在代码中引发事件来发送回进度报告。首先创建一个存根方法来调用事件:
protected virtual void OnProgress(ProgressEventArgs e)
{
var handler = Progress;
if (handler != null)
{
handler(this, e);
}
}
然后将其添加到任务代码中所需的位置:
OnProgress(new ProgressEventArgs(2452343, 10, "Reindexing google..."));
唯一要注意的事情是少量报告进度,因为每次事件触发时,它都会中断控制台应用程序,并且如果一次发送1000万个事件,那么您真的会陷入困境。对此要合乎逻辑。
答案 3 :(得分:0)
替代方式;如果您拥有代码IProgress<T>
和Progress
IProgress<T>
{
IProgress<T> CreateNew();
Report(T progress);
}
Progress<T> : IProgress<T>
{
Progress(ShellProgressClass)
{
// initialize progressBar or span new
}
....
IProgress<T> CreateNew()
{
return new Progress();
}
}
您以后可以即兴制作一个大的progressBar(顺序或并行集合),而没有
答案 4 :(得分:0)
您的MyService可能具有类似于以下的依赖项:
public interface IJobContainer
{
void Add(IJob job);
void RunJobs(IProgress<MyProgress> progress, Action<IJob>? callback = null); // Using an action for extra work you may want to do
}
通过这种方式,您不必担心MyService中的报告进度(无论如何,这似乎都不应该是MyService的工作。对于并行作业容器,实现看起来可能像这样:
public class MyParallelJobContainer
{
private readonly IList<IJob> parallelJobs = new List<IJob>();
public MyParallelJobContainer()
{
this.progress = progress;
}
public void Add(IJob job) { ... }
void RunJobs(IProgress<MyProgress> progress, Action<IJob>? callback = null)
{
using (var progressBar = new ProgressBar(options...))
{
Parallel.ForEach(parallelJobs, job =>
{
callback?.Invoke(job);
job.Execute();
progressBar.Tick();
})
}
}
}
MyService如下所示:
public class MyService : IMyService
{
private readonly IJobContainer sequentialJobs;
private readonly IJobContainer parallelJobs;
public MyService(
IJobContainer sequentialJobs,
IJobContainer parallelJobs)
{
this.sequentialJobs = sequentialJobs;
this.parallelJobs = parallelJobs;
this.sequentialJobs.Add(new DoSequentialJob1());
this.sequentialJobs.Add(new DoSequentialJob2());
this.sequentialJobs.Add(new DoSequentialJob3));
this.parallelJobs.Add(new DoParallelJobA());
this.parallelJobs.Add(new DoParallelJobB());
this.parallelJobs.Add(new DoParallelJobC());
}
public void RunJobs(IProgress<MyCustomProgress> progress)
{
sequentialJobs.RunJobs(progress, job =>
{
// do something with the job if necessary
});
parallelJobs.RunJobs(progress, job =>
{
// do something with the job if necessary
});
}
这种方法的优点是MyService仅拥有一份工作,而不必担心完成工作后会做什么。
答案 5 :(得分:0)
据我对您问题的理解,问题是如何显示同步作业和并行作业的完成情况。
理论上,并行作业可以同时开始和完成,因此您可以将并行作业视为单个作业。与其将连续作业的数量用作总数,不如将其增加一。对于少量并行作业,这可能是令人满意的。
如果要在并行作业之间添加进度,则将需要在代码中处理多线程,因为并行作业将同时运行 。
object pJobLock = new object();
int numProcessed = 0;
foreach(var parallelJob in parallelJobs)
{
parallelJob.DoWork();
lock (pJobLock)
{
numProcessed++;
progress.Report(new MyCustomProgress { Current = numProcessed, Total = parallelJobs.Count() });
}
}