.net core - передача неизвестного числа IProgress <T>в библиотеку классов - PullRequest
0 голосов
/ 16 февраля 2019

У меня есть консольное приложение, которое использует библиотеку классов для выполнения некоторых длительных задач.Это консольное приложение с ядром .net, использующее базовый хост .net core.Я также использую библиотеку 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

Любая помощь высоко ценится.

Ответы [ 6 ]

0 голосов
/ 27 февраля 2019

Насколько я понимаю вашу проблему, вопрос заключается в том, как вы отображаете прогресс за и завершение синхронных и параллельных заданий.

Теоретически параллельные задания могут начинаться и заканчиваться вв то же время, чтобы вы могли рассматривать параллельные задания как одну работу.Вместо того, чтобы использовать количество последовательных заданий как общее, увеличьте это число на единицу.Это может быть удовлетворительным для небольшого числа параллельных заданий.

Если вы хотите добавить прогресс между параллельными заданиями, вам потребуется обрабатывать многопоточность в вашем коде, потому что параллельные задания будут выполняться одновременно .

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() });
    }
}
0 голосов
/ 24 февраля 2019

Ваш 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 имеет только одну работу ине нужно беспокоиться о том, что вы делаете после завершения работы.

0 голосов
/ 23 февраля 2019

Альтернативный способ;Если у вас есть код 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();
   }
}

, вы можете позже импровизировать, чтобы иметь один большой прогрессбар (набор последовательных или параллельных), а не

0 голосов
/ 20 февраля 2019

Честно говоря, я бы просто использовал событие в вашем прототипе задачи.

Не совсем понятно, что именно вы хотите, потому что код, который вы разместили, не соответствует именам, на которые вы ссылаетесь в тексте вашего вопроса ...Было бы полезно иметь весь код (например, функцию 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
}

И подпишитесь на библиотеку классовevent:

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..."));

Единственное, что нужно соблюдать осторожность, эторедко сообщайте о прогрессе, потому что каждый раз, когда ваше событие запускается, оно прерывает ваше консольное приложение, и вы действительно можете сильно его затормозить, если отправите 10 миллионов событий одновременно.Будьте логичны с этим.

0 голосов
/ 16 февраля 2019

Можете ли вы изменить его следующим образом?


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, вероятно, есть получатель прогресса.Реально IProgress, вероятно, следует вводить с помощью конструкторов Tasks.Но дело в том, что ваш общедоступный интерфейс не принимает список задач, поэтому он должен просто возвращать коллекцию отчетов о проделанной работе.

Как внедрить репортеров в ваши задачи - это отдельная история, которая зависит от реализации задач иможет или не может быть поддержано.«из коробки».

Однако вам, вероятно, следует предоставить обратный вызов прогресса или фабрику прогресса, чтобы создавались репортеры прогресса по вашему выбору:


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).

0 голосов
/ 16 февраля 2019

Меня немного смущает ваше описание, но давайте посмотрим, пойму ли я, чем вы занимаетесь.Так что, если вы оберните все это в класс, то taskList1 и taskList2 могут быть переменными класса.(Между прочим, taskList1 / 2 должен быть назван лучше: скажем, parallelTaskList и все такое ... в любом случае.) Затем вы можете написать новый метод для класса CheckTaskStatus () и просто перебрать две переменные класса.Это помогает или я полностью пропустил ваш вопрос?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...