.net核心-将未知数量的IProgress <t>传递给类库

时间:2019-02-16 00:33:33

标签: c# .net asp.net-core .net-core

我有一个控制台应用程序,它使用类库执行一些长期运行的任务。这是一个.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看起来像这样(Job1Job2Job3实现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时,我需要能够创建一个新的子级ShellProgressBarShellProgressBar.Spawn)并将其显示为子级进度条,比如说“并行作业”。

这是我在寻求有关如何实现此目标的帮助的地方。

注意:我不想依赖包含ShellProgressBar的类库中的MyService

非常感谢任何帮助。

6 个答案:

答案 0 :(得分:0)

您的描述让我有些困惑,但是让我看看我是否了解您的工作。因此,如果将所有这些都包装在一个类中,那么taskList1和taskList2可能是类变量。 (通过这种方式,可以更好地命名taskList1 / 2:说出parallelTask​​List等等。)然后,您可以在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() });
    }
}