私はチームのビルドツールとして使用する古いWPFアプリケーションを変更しています。 1つのコンポーネントは、MSBuild ILoggerを介してビルドログテキストを渡すFlowDocumentを含むLoggingWindowクラスです。ビルドが完了する頃には、典型的には1000ページを超えることになり、ビルドが進むにつれて100k行以上のログテキストを受け取ることになります。非常に大量のテキストが渡されるため、うまく機能しません。これは、私たちが今、これを処理する方法である:文字列のコレクションを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のバッチでメッセージを取得し、メッセージがある場合は10 msの間スリープし、そうでない場合は100 msの間スリープしますメインスレッドをあまりにもブロックしないようにします。それは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メソッドは、メインスレッド上で実行する必要がありますので、私たちは、段落を追加する前に、ディスパッチャをチェックする必要があります。
私はのObservableCollectionにLogMessageにを追加して、WPFに実際のUIの更新を延期、おそらくより良いそれを扱うなることができるように、バインディングWPFデータを利用した溶液に、これをリファクタリングしたいと思います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の初心者であり、バインディングに新しい方でもあるので、どうやってこれをやっていくのかは分かりません。
また、誰かが私がやろうとしていることを能動的に行う方法についてより良い提案があれば、それは素晴らしいことです。私の目標は、メインスレッドをブロックするのではなく、可能な限り多くのログメッセージのペースに追いつくことができるようにすることです。