使用 IProgress.Report() 报告进度时,控制台消息的显示顺序不正确

Console messages appearing in incorrect order when reporting progress with IProgress.Report()

我注意到以下行为。由 IProgress 填充时,控制台输出消息出现在错误的文件夹中。

var recounter = new IdRecounter(filePath, new Progress<string>(Console.WriteLine));
                recounter.RecalculateIds();

我正在努力提高我的封装、可重用性和设计技能。 所以,我有一个名为 IdRecounter 的 class。我现在想在控制台应用程序中使用它,但稍后可能会在 WPF 应用程序或其他应用程序中使用它。

因此,我希望 class 完全不知道它的环境 - 但同时我想报告操作的进度 'live' - 因此,我使用的是 IProgress 类型,这将允许我将内容放入控制台或日志文件,或更新状态标签 属性 等。(如果您不是这样做的,请告诉我)

所以,我注意到它倾向于以错误的顺序将消息抛出到控制台,例如 处理文件 1
处理文件 4
处理文件 5
处理文件 3
全部完成!
处理文件 2

当我将 IProgress (MyProgress.Report()) 切换为 Console.WriteLine() 时,它按预期工作。
这是什么原因,如何控制?

谢谢

Progress<T> class 使用创建它的线程的当前同步上下文来为其 ProgressChanged 事件调用事件处理程序。

在控制台应用程序中,默认同步上下文使用线程池来调用委托,而不是将它们编组回检索上下文的线程。这意味着每次更新进度时,事件处理程序可能会在不同的线程中调用(特别是如果进度更新快速连续发生)。

由于线程的调度方式,无法保证一个线程池工作者在另一个线程池工作者实际上 运行 之前分配任务另一个工人 运行 是它的任务。特别是对于相对简单的任务(例如发出进度消息),很容易出现后来排队的任务实际上在较早排队的任务之前完成的情况。

如果您希望保证按顺序显示您的进度更新消息,您需要使用不同的机制。例如,您可以设置 producer/consumer 和 BlockingCollection<T>,其中您有一个线程使用由报告进度的操作排队(生成)的消息。或者,当然,您可以直接调用 Console.WriteLine()(因为您已经验证过会起作用)。

请注意,这并不意味着您需要放弃使用 IProgress<T> 的想法。这只是意味着您需要提供自己的实现,而不是使用 Progress<T> class,至少在控制台场景中是这样。例如:

class ConsoleProgress : IProgress<string>
{
    public void ReportProgress(string text)
    {
        Console.WriteLine(text);
    }
}

例如,这将允许您将 IProgress<T> 抽象保留在 IdRecounter() class 中,将该类型与 UI 上下文分离。它可以重复用于控制台程序以及任何 GUI API 程序,例如 Winforms、WPF、Winrt 等


底线:当您需要抽象 GUI 程序中所需的跨线程、同步上下文相关操作时,Progress<T>IProgress<T> 的一个非常有用的实现。它将在控制台程序中工作,但因为在这种情况下它将使用线程池,所以您可能无法获得确定性排序的输出,至少在不包括与 ProgressChanged 事件处理程序的额外同步的情况下不会。

此 class 模仿控制台应用程序 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);
    }
}

只有action,但是像Progress<T>那样的事件实现也很简单