Консольные сообщения, появляющиеся в неправильном порядке, сообщая о прогрессе с IProgress.Report()
Я заметил следующее поведение. Выходные сообщения консоли отображаются в неверной папке, если они заполнены IProgress.
var recounter = new IdRecounter(filePath, new Progress<string>(Console.WriteLine));
recounter.RecalculateIds();
Я пытаюсь улучшить свои навыки инкапсуляции, повторного использования и дизайна. Итак, у меня есть класс с именем IdRecounter. Сейчас я хочу использовать его в консольном приложении, но позже, возможно, в приложении WPF или чем-то еще.
Поэтому я хочу, чтобы класс полностью не знал о своей среде - но в то же время я хочу сообщать о ходе действия "вживую" - поэтому я использую тип IProgress, который позволит мне помещать вещи в консоль, или в файл журнала или обновите свойство метки состояния и т. д. (пожалуйста, дайте мне знать, если вы не так делаете)
Итак, я заметил, что он склонен выбрасывать сообщения в консоль в неправильном порядке, например, Обработка файла 1
Обработка файла 4
Обработка файла 5
Обработка файла 3
Все сделано!
Обработка файла 2
Когда я переключил IProgress (MyProgress.Report()
) с Console.WriteLine()
это работает как ожидалось.
В чем причина этого и как это можно контролировать?
Спасибо
2 ответа
Progress<T>
Класс использует текущий контекст синхронизации для потока, в котором он был создан, для вызова обработчиков событий для его ProgressChanged
событие.
В консольном приложении контекст синхронизации по умолчанию использует пул потоков для вызова делегатов, а не перенаправляет их обратно в поток, где извлекается контекст. Это означает, что каждый раз, когда вы обновляете прогресс, обработчик событий может вызываться в другом потоке (особенно, если обновления прогресса происходят в быстрой последовательности).
Из-за способа планирования потоков нет никакой гарантии, что работник пула потоков назначил задачу до того, как другой работник пула потоков выполнит свою задачу до того, как другой работник выполнит свою задачу. Особенно для относительно простых задач (таких как выдача сообщений о ходе выполнения) может легко случиться так, что задачи, поставленные в очередь позже, фактически будут завершены, прежде чем задачи, поставленные в очередь ранее.
Если вы хотите, чтобы ваши сообщения об обновлениях гарантированно отображались по порядку, вам нужно использовать другой механизм. Например, вы можете настроить производителя / потребителя с помощью BlockingCollection<T>
где у вас есть один поток, потребляющий сообщения, которые ставятся в очередь (производятся) вашими операциями, которые сообщают о прогрессе. Или, конечно, вы могли бы просто позвонить Console.WriteLine()
напрямую (как вы уже убедились, будет работать).
Обратите внимание, что это не означает, что вам нужно отказаться от идеи использования IProgress<T>
, Это просто означает, что вам нужно будет предоставить собственную реализацию, а не использовать Progress<T>
класс, по крайней мере, в консольном сценарии. Например:
class ConsoleProgress : IProgress<string>
{
public void ReportProgress(string text)
{
Console.WriteLine(text);
}
}
Это позволит вам, например, сохранить IProgress<T>
абстракция в IdRecounter()
класс, отделяя этот тип от контекста пользовательского интерфейса. Его можно использовать для консольной программы, а также для любой программы с графическим интерфейсом API, такой как Winforms, WPF, Winrt и т. Д.
Суть: Progress<T>
это очень полезная реализация IProgress<T>
когда вам необходимо абстрагировать межпотоковые операции синхронизации, связанные с контекстом, которые необходимы в программе с графическим интерфейсом. Он будет работать в консольных программах, но поскольку он будет использовать пул потоков в этом случае, вы можете не получить детерминированно упорядоченный вывод, по крайней мере, без включения дополнительной синхронизации с ProgressChanged
обработчики событий.
Этот класс имитирует основное поведение Progress<T>
для консольного приложения:
public class ConsoleProgress<T> : IProgress<T>
{
private Action<T> action;
public ConsoleProgress(Action<T> action)
{
this.action = action;
}
public void Report(T value)
{
action(value);
}
}
Есть только действие, но реализация события, как в Progress<T>
было бы просто тоже.