我正在修改我们在团队中用作构建工具的较早的WPF应用程序。一个组件是一个LoggingWindow类,它包含一个FlowDocument,我们将构建日志文本传递给MSBuild ILogger。在构建完成时,文档通常最终会超过1000页,随着构建的进行接收超过10万行日志文本。由于文本传递量很大,因此表现不佳。这是现在我们如何处理这个问题:将字符串集合绑定到FlowDocument
这是LoggingWindow类的XAML:
<FlowDocumentPageViewer Name="LogPageViewer" Width="Auto" Height="Auto">
<FlowDocument Name="LogDocument" ColumnWidth="800" Foreground="LightGray" Background="Black" FontSize="12" FontFamily="Consolas" TextAlignment="Left">
<FlowDocument.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0"></Setter>
</Style>
</FlowDocument.Resources>
</FlowDocument>
</FlowDocumentPageViewer>
我们旋转了一个任务轮询消息ConcurrentQueue。正如你所看到的,我不知道如何向文档中添加消息,所以如果有消息,我会分批抓取消息并休眠10毫秒,如果没有消息,则休眠100毫秒不要太多地阻塞主线程。因为这是拥有该FlowDocument的线程
public void Start()
{
_cts = new CancellationTokenSource();
_uiProcessor = Task.Factory.StartNew(() => UpdateUi(_cts.Token));
}
private void UpdateUi(CancellationToken context)
{
while (!context.IsCancellationRequested)
{
if (_messageQueue.Count > 0)
{
var count = Math.Min(_messageQueue.Count, 10);
for (var i = 0; i < count; i++)
{
LogMessage message;
_messageQueue.TryDequeue(out message);
AddText(message);
}
// there are likely to be more messages, so only sleep for 10 ms
Thread.Sleep(10);
}
else
{
// there aren't likely to be more messages yet, so we can sleep for 100 ms
Thread.Sleep(100);
}
}
}
的AddText方法必须在主线程上运行,因此我们需要增加一款之前检查调度。
private void AddText(LogMessage message)
{
if (Dispatcher.CheckAccess())
{
try
{
var timestampText = $"{message.Timestamp.ToString("MM/dd/yyyy HH:mm:ss.fff")}:{new string(' ', message.Indent * 2)}";
var span = new Span
{
FontFamily = new FontFamily("Consolas"),
FontStyle = FontStyles.Normal,
FontWeight = FontWeights.Normal,
FontStretch = FontStretches.Normal,
FontSize = 12,
Foreground = new SolidColorBrush(Color.FromArgb(0xff, 0xd3, 0xd3, 0xd3))
};
span.Inlines.Add(new Run(timestampText) { Foreground = new SolidColorBrush(Colors.White) });
span.Inlines.Add(new Run(message.Message) { Foreground = new SolidColorBrush(message.Color), FontWeight = message.Weight });
var paragraph = new Paragraph(span);
LogDocument.Blocks.Add(paragraph);
if (AutoScrollMenuItem.IsChecked)
{
LogPageViewer.LastPage();
}
}
catch (Exception ex)
{
_errorIndex++;
using (var fs = File.OpenWrite($"FormatError-{_errorIndex:00}.txt"))
{
var sw = new StreamWriter(fs)
{
AutoFlush = true
};
sw.WriteLine("Error: ");
sw.WriteLine(ex);
sw.WriteLine();
sw.Write(message.Message);
fs.Close();
}
}
}
else
{
Dispatcher.Invoke(new Action<LogMessage>(AddText), message);
}
}
我想重构这个的解决方案,使利用WPF数据绑定,这样我可以添加的LogMessage到一个ObservableCollection并推迟实际UI更新WPF,这可能会处理它优于我可以手动。不过,我是WPF新手,甚至更新绑定,所以我不太确定我会如何去做这件事。另外,如果任何人有什么更好的建议,以如何执行我想要做的事情,这将是伟大的。我的目标是能够尽可能地跟上日志消息的添加速度,同时不会阻塞主线程。