【问题标题】:Console messages appearing in incorrect order when reporting progress with IProgress.Report()使用 IProgress.Report() 报告进度时,控制台消息显示顺序不正确
【发布时间】:2016-02-15 09:46:18
【问题描述】:

我注意到以下行为。当 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() 时,它按预期工作。
这是什么原因,如何控制?

谢谢

【问题讨论】:

    标签: c# console-application iprogress


    【解决方案1】:

    此类模仿控制台应用程序Progress&lt;T&gt; 的基本行为:

    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&lt;T&gt; 中的事件实现也会很简单。

    【讨论】:

      【解决方案2】:

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

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

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

      如果您希望确保按顺序显示进度更新消息,则需要使用不同的机制。例如,您可以使用BlockingCollection&lt;T&gt; 设置一个生产者/消费者,其中您有一个单线程消费消息,这些消息由您报告进度的操作排队(产生)。或者,当然,您可以直接致电Console.WriteLine()(因为您已经验证过可以使用)。

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

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

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


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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-02-13
        • 2016-04-04
        • 1970-01-01
        • 1970-01-01
        • 2021-07-06
        • 2020-12-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多