Вопрос: Сообщения консоли отображаются в неправильном порядке, когда сообщают о прогрессе с помощью 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() он работает так, как ожидалось.
В чем причина этого и как его можно контролировать?

благодаря


6


источник


Ответы:


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> когда вам нужно абстрагироваться от кросс-потоков, связанных с синхронизацией операций, которые необходимы в программе GUI. Он будет работать в консольных программах, но поскольку он будет использовать пул потоков в этом случае, вы можете не получить детерминистически упорядоченный вывод, по крайней мере, не включая дополнительную синхронизацию с ProgressChanged обработчики событий.


8



Этот класс имитирует основное поведение 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> было бы просто.


0