【发布时间】:2017-02-24 23:05:15
【问题描述】:
我正在调整一个较旧的 WPF 应用程序,我们将其用作我团队的构建工具。一个组件是一个包含 FlowDocument 的 LoggingWindow 类,我们通过 MSBuild ILogger 将构建日志文本传递给该类。到构建完成时,文档通常会超过 1000 页,在构建过程中会收到超过 100k 行的日志文本。由于传递的文本数量庞大,它的性能不是很好。这就是我们现在的处理方式:
这是 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 条为一组抓取消息,如果有消息则休眠 10 毫秒,如果没有则休眠 100 毫秒,以尝试不要过多地阻塞主线程。
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 方法必须在主线程上运行,因为那是拥有 FlowDocument 的线程,所以我们需要在添加段落之前检查 Dispatcher。
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 的新手,甚至是绑定的新手,所以我不确定我将如何去做。
另外,如果有人对如何高效地做我想做的事情有更好的建议,那就太好了。我的目标是在不阻塞主线程的情况下尽可能跟上日志消息的添加速度。
【问题讨论】:
标签: c# wpf data-binding flowdocument