@Nubzilo
Изучаю C#

Как правильно информировать о процессе выполнения задачи?

Добрый день. Как правильно и красиво уведомлять о ходе выполнения задачи? Например, уведомлять progressbar и выводить сообщение на textbox на winforms?
К примеру такой класс
class WorkClass
{
	public async Task LongMethod(List<string> somethink)
	{
	 await Task.Factory.StartNew(() =>
	 {
//отсюда делать вывод somethink.Count()
		foreach (string element in somethink)
		{
			Thread.Sleep(5000);
			//тут делать вывод о выполнении
		}
	 });
	}
}


Есть вариант делать через события. Но создавать как минимум 3 события для того, что б информаровать и настроить прогрессбар мне кажется не совсем правильным.
Пробовал через класс Progress, но тут мне не понравилось плодить большое кол во классов через
Progress.Report(new MyReport{...});

В общем как эту задачу решают умные люди?
  • Вопрос задан
  • 3326 просмотров
Решения вопроса 1
lam0x86
@lam0x86
Пробовал через класс Progress, но тут мне не понравилось плодить большое кол во классов через
Progress.Report(new MyReport{...});

Ничего страшного в этом нет - эти объекты на самом деле тут же умирают, не покидая первого поколения GC.
Тут другой вопрос более важен: класс Progress устроен так, что маршаллит все репорты в SynchronizationContext, в котором он был создан. В случае с WinForms - это контекст UI-потока. Если ваш Task слишком часто будет вызывать метод Report, это может негативно сказаться на UI-потоке, т.к. он будет забит репортами, каждый из которых он должен обработать. Более того, поскольку маршаллинг в классе Progress асинхронный, есть вероятность, что задача будет выполнена до того, как на UI-потоке обработаются все репорты. Получется запаздывание прогресс-бара от реального прогресса (и вот тут как раз возникает вероятность перехода репортов в Gen1 и Gen2).

Я бы написал свою реализацию класса Progress, которая использует таймер, периодически опрашивающий последнее обновление прогресса. Так сделано в большинстве систем, где обновление может происходить часто, например, при копировании множества файлов. Примерно так:

public interface IProgressInfo
    {
        bool IsCompleted { get; }
    }

    public class ProgressInfo : IProgressInfo
    {
        public ProgressInfo(double completedPercentage, string progressStatusText)
        {
            CompletedPercentage = completedPercentage;
            ProgressStatusText = progressStatusText;
        }

        public double CompletedPercentage { get; private set; }

        public string ProgressStatusText { get; private set; }

        public bool IsCompleted
        {
            get { return CompletedPercentage >= 1; }
        }
    }

    public class Progress<T> : IProgress<T> where T : class, IProgressInfo
    {
        private T _previousProgressInfo;
        private volatile T _progressInfo;
        private readonly Action<T> _updateProgressAction;
        private readonly Timer _timer;
        private readonly SynchronizationContext _synchronizationContext;

        public Progress(TimeSpan pollingInterval, Action<T> updateProgressAction)
        {
            _synchronizationContext = SynchronizationContext.Current ?? new SynchronizationContext();
            _updateProgressAction = updateProgressAction;
            _timer = new Timer(TimerCallback, null, pollingInterval, pollingInterval);
        }

        private void TimerCallback(object state)
        {
            ProcessUpdate();
        }

        private void ProcessUpdate()
        {
            var progressInfo = _progressInfo;
            if (_previousProgressInfo != progressInfo)
            {
                _synchronizationContext.Send(state => _updateProgressAction((T) state), progressInfo);
            }
            _previousProgressInfo = progressInfo;
        }

        public void Report(T value)
        {
            _progressInfo = value;
            if (value.IsCompleted)
            {
                _timer.Dispose();
                ProcessUpdate();
            }
        }
    }


Использовать его можно примерно так:

var workClass = new WorkClass();

            var list = Enumerable.Range(1, 1000).Select(i => i.ToString()).ToArray();
            var progress = new Progress<ProgressInfo>(TimeSpan.FromMilliseconds(100), UpdateProgressAction);

            await workClass.LongMethod(list, progress);


где:

private void UpdateProgressAction(ProgressInfo obj)
        {
            ProgressBar.Value = obj.CompletedPercentage;
            OperationStatus.Text = obj.ProgressStatusText;
            ProgressBarPercentage.Text = string.Format("{0:P}", obj.CompletedPercentage);
        }

internal class WorkClass
    {
        public async Task LongMethod(IReadOnlyList<string> something, IProgress<ProgressInfo> progress)
        {
            await Task.Factory.StartNew(() =>
            {
                var count = something.Count;
                for (int i = 0; i < count; i++)
                {
                    var element = something[i];
                    Thread.Sleep(5);
                    progress.Report(new ProgressInfo((double)(i + 1) / count, element));
                }
            });
        }
    }
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@Sumor
Практическое руководство. Фоновое выполнение операции

Для выполнения продолжительной задачи нужно использовать BackgroundWorker.
В событии DoWork описываете вашу длительную работу.
В процессе работы (в обработчике DoWork) вызываете метод ReportProgress, чтобы сообщить в основную программу о состоянии обработки. В основную программу приходит событие ProgressChanged.
Периодически отслеживаете не пытается ли кто остановить вашу работу через CancellationPending.

Для запуска работы используете метод RunWorkerAsync. Через событие ProgressChanged отслеживаете прогресс и обновляете прогрессбар. По событию RunWorkerCompleted узнаёте о завершении или прерывании работы.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы