8

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

谢谢

4

2 回答 2

12

该类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()类中,将该类型与 UI 上下文分离。它可以重用于控制台程序以及任何 GUI API 程序,例如 Winforms、WPF、Winrt 等。


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

于 2015-11-13T18:17:57.120 回答
2

此类模仿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);
    }
}

只有动作,但就像 in 一样的事件实现Progress<T>也很简单。

于 2016-05-18T20:05:23.600 回答