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

本文关键字:控制台 消息 不正确 顺序 Report IProgress 报告 使用 | 更新日期: 2023-09-27 18:21:54

我注意到了以下行为。当控制台输出消息由IProgress填充时,它们出现在不正确的文件夹中

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

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

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

所以,我注意到它倾向于以不正确的顺序将消息放入控制台,例如。正在处理文件1
正在处理文件4
正在处理文件5
正在处理文件3
全部完成
处理文件2

当我用Console.WriteLine()切换IProgress(MyProgress.Report())时,它按预期工作
造成这种情况的原因是什么?如何控制这种情况?

感谢

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

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

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

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

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

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

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

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


最重要的是:当您需要抽象GUI程序中所需的跨线程、同步上下文相关操作时,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>中一样)也很简单。