我注意到以下行为。当IProgress填充控制台输出消息时,它们会显示在错误的文件夹中。
var recounter = new IdRecounter(filePath, new Progress<string>(Console.WriteLine));
recounter.RecalculateIds();
我正在努力提高我的封装,可重用性和设计技巧。 所以,我有一个名为IdRecounter的类。我现在想在控制台应用程序中使用它,但稍后可能在WPF应用程序中使用它。
因此,我希望班级完全不知道它的环境 - 但同时我想报告“现场”行动的进展 - 因此,我使用IProgress类型,这将允许我把东西放入控制台,或日志文件,或更新状态标签属性等。(如果你不这样做,请告诉我)
所以,我注意到它倾向于以错误的顺序将消息抛入控制台,例如
处理文件1
处理文件4
处理文件5
处理文件3
一切都完成了!
处理文件2
当我使用MyProgress.Report()
切换IProgress(Console.WriteLine()
)时,它按预期工作
这是什么原因以及如何控制?
由于
答案 0 :(得分:10)
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等。
底线:Progress<T>
是一个非常有用的IProgress<T>
实现,当您需要抽象GUI程序中需要的跨线程,同步上下文相关操作时。它可以在控制台程序中工作,但由于在这种情况下它将使用线程池,因此您可能无法获得确定性有序的输出,至少在没有包含ProgressChanged
事件处理程序的其他同步的情况下。
答案 1 :(得分: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);
}
}
只有行动,但与Progress<T>
一样的事件实施也很简单。