C# 使用IProgress.Report()报告进度时,控制台消息的顺序不正确
我注意到以下行为。当控制台输出消息由IProgress填充时,它们出现在不正确的文件夹中。C# 使用IProgress.Report()报告进度时,控制台消息的顺序不正确,c#,console-application,iprogress,C#,Console Application,Iprogress,我注意到以下行为。当控制台输出消息由IProgress填充时,它们出现在不正确的文件夹中。 var recounter = new IdRecounter(filePath, new Progress<string>(Console.WriteLine)); recounter.RecalculateIds(); var-reconnenter=new-idreconter(文件路径,新进度(Console.WriteLine)); 重新计算中心。重
var recounter = new IdRecounter(filePath, new Progress<string>(Console.WriteLine));
recounter.RecalculateIds();
var-reconnenter=new-idreconter(文件路径,新进度(Console.WriteLine));
重新计算中心。重新计算EIDS();
我正在努力提高我的封装性、可重用性和设计技能。
所以,我有一个叫做IdRecounter的类。我现在想在控制台应用程序中使用它,但以后可能会在WPF应用程序中使用
因此,我希望类完全不知道它的环境-但同时我想报告“live”操作的进度-因此,我使用的是IProgress类型,它允许我将内容放入控制台、日志文件或更新状态标签属性等(如果您不是这样做的,请告诉我)
所以,我注意到它倾向于以错误的顺序将消息抛出控制台,例如。
正在处理文件1处理文件4
处理文件5
正在处理文件3
全部完成
正在处理文件2
当我将IProgress(
MyProgress.Report()
)与Console.WriteLine()切换时,它会按预期工作。
原因是什么?如何控制
感谢该类使用创建它的线程的当前同步上下文为其ProgressChanged
事件调用事件处理程序
在控制台应用程序中,默认同步上下文使用线程池调用委托,而不是将委托封送回检索上下文的线程。这意味着每次更新进度时,可能会在不同的线程中调用事件处理程序(特别是在进度更新快速连续发生的情况下)
由于线程的调度方式,无法保证在另一个线程池工作线程实际运行其任务之前,已分配任务的线程池工作线程将在该另一个工作线程运行其任务之前运行其任务。特别是对于相对简单的任务(例如发出进度消息),很容易出现这样的情况,即稍后排队的任务实际上是在较早排队的任务之前完成的
如果希望确保进度更新消息按顺序显示,则需要使用不同的机制。例如,您可以使用BlockingCollection
设置生产者/消费者,其中您有一个线程使用由报告进度的操作排队(生成)的消息。当然,您也可以直接调用Console.WriteLine()
(正如您已经验证的那样)即可
请注意,这并不意味着您需要放弃使用IProgress
的想法。这只是意味着您需要提供自己的实现,而不是使用Progress
类,至少在控制台场景中是这样。例如:
class ConsoleProgress : IProgress<string>
{
public void ReportProgress(string text)
{
Console.WriteLine(text);
}
}
class控制台进程:i进程
{
公共void报告进度(字符串文本)
{
控制台写入线(文本);
}
}
例如,这将允许您在IdRecounter()
类中保留IProgress
抽象,将该类型与UI上下文分离。它可以用于控制台程序以及任何GUI API程序,如Winforms、WPF、Winrt等
底线:Progress
是IProgress
的一个非常有用的实现,当您需要抽象GUI程序中所需的跨线程、同步上下文相关操作时。它将在控制台程序中工作,但由于在这种情况下它将使用线程池,您可能无法获得确定顺序的输出,至少在不包括对ProgressChanged
事件处理程序的额外同步的情况下是如此。该类模拟了控制台应用程序的Progress
的基本行为:
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);
}
}
公共类控制台进程:IProgress
{
私人行动;
公共控制台进程(操作)
{
这个动作=动作;
}
公共作废报告(T值)
{
作用(价值);
}
}
只有操作,但事件实现也很简单,就像Progress
中一样