154 lines
7.1 KiB
C#
154 lines
7.1 KiB
C#
using System.Diagnostics;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Input;
|
||
using System.Windows.Media;
|
||
using System.Windows.Threading;
|
||
using Main.Test.ViewModels;
|
||
using Trade.Framework.Controls;
|
||
|
||
namespace Main.Test;
|
||
|
||
public partial class MainWindow : WindowBase
|
||
{
|
||
public MainWindow()
|
||
{
|
||
InitializeComponent();
|
||
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
|
||
timer.Tick += (_, _) => ImageStatisticsText.Text = ThumbnailLoader.Statistics;
|
||
Loaded += (_, _) => timer.Start();
|
||
Closed += (_, _) => timer.Stop();
|
||
}
|
||
|
||
private async void LoadData_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
SetBusy(true);
|
||
try
|
||
{
|
||
var count = int.Parse((string)((ComboBoxItem)DataSize.SelectedItem).Tag);
|
||
var watch = Stopwatch.StartNew();
|
||
var vm = (MainViewModel)DataContext;
|
||
vm.NetworkImageSize = int.Parse((string)((ComboBoxItem)ImageMode.SelectedItem).Tag);
|
||
await vm.LoadAsync(count);
|
||
OrdersGrid.ScrollIntoView(OrdersGrid.Items[0]);
|
||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||
PerformanceText.Text = $"加载 {count:N0} 行(含生成与首次布局):{watch.Elapsed.TotalMilliseconds:N0} ms。可继续测试滚动和全选。";
|
||
}
|
||
catch (Exception ex) { PerformanceText.Text = $"加载失败:{ex.Message}"; }
|
||
finally { SetBusy(false); }
|
||
}
|
||
|
||
private async void Benchmark_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
SetBusy(true);
|
||
try { PerformanceText.Text = await RunPerformanceTestAsync(); }
|
||
catch (Exception ex) { PerformanceText.Text = $"测试失败:{ex.Message}"; }
|
||
finally { SetBusy(false); }
|
||
}
|
||
|
||
private async void ImageBenchmark_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
SetBusy(true);
|
||
try { PerformanceText.Text = await RunImagePerformanceTestAsync(); }
|
||
catch (Exception ex) { PerformanceText.Text = $"图片测试失败:{ex.Message}"; }
|
||
finally { SetBusy(false); }
|
||
}
|
||
|
||
public async Task<string> RunImagePerformanceTestAsync()
|
||
{
|
||
if (OrdersGrid.Items.Count == 0 || ((MainViewModel)DataContext).NetworkImageSize == 0)
|
||
return "请先选择 Picsum 图片模式并点击加载数据,再运行图片滚动测试。";
|
||
var viewer = FindVisual<ScrollViewer>(OrdersGrid).First();
|
||
var offset = viewer.VerticalOffset;
|
||
var samples = new[] { new List<double>(), new List<double>() };
|
||
var heartbeat = Stopwatch.StartNew();
|
||
var lastTick = heartbeat.Elapsed.TotalMilliseconds;
|
||
var maxDelay = 0d;
|
||
var pulse = new DispatcherTimer(DispatcherPriority.Normal) { Interval = TimeSpan.FromMilliseconds(50) };
|
||
pulse.Tick += (_, _) =>
|
||
{
|
||
var now = heartbeat.Elapsed.TotalMilliseconds;
|
||
maxDelay = Math.Max(maxDelay, Math.Max(0, now - lastTick - 50));
|
||
lastTick = now;
|
||
};
|
||
pulse.Start();
|
||
try
|
||
{
|
||
viewer.ScrollToHorizontalOffset(0);
|
||
for (var pass = 0; pass < 2; pass++)
|
||
{
|
||
PerformanceText.Text = pass == 0 ? "首次滚动:网络图片正在加载…" : "重复访问相同位置:比较缓存与重新加载…";
|
||
for (var i = 0; i < 40; i++)
|
||
{
|
||
var watch = Stopwatch.StartNew();
|
||
viewer.ScrollToVerticalOffset(i % 24);
|
||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||
samples[pass].Add(watch.Elapsed.TotalMilliseconds);
|
||
await Task.Delay(150);
|
||
}
|
||
}
|
||
// 有界等待当前可见图片,避免把尚未完成的请求当作加载成功。
|
||
var wait = Stopwatch.StartNew();
|
||
while (ThumbnailLoader.Pending > 0 && wait.Elapsed < TimeSpan.FromSeconds(8)) await Task.Delay(100);
|
||
var first = samples[0].Order().ToArray(); var revisit = samples[1].Order().ToArray();
|
||
return $"Picsum {((MainViewModel)DataContext).NetworkImageSize}px / {OrdersGrid.Items.Count:N0} 行 | 首次滚动均值 {first.Average():N1} ms / P95 {first[37]:N1} ms | 复访均值 {revisit.Average():N1} ms / P95 {revisit[37]:N1} ms | UI 心跳最大延迟 {maxDelay:N1} ms(非帧率);图片统计为本次进程累计,取消表示滚出视口。";
|
||
}
|
||
finally { pulse.Stop(); viewer.ScrollToVerticalOffset(offset); }
|
||
}
|
||
|
||
// 测量 UI 线程滚动与布局响应,不等同于 GPU 帧率。
|
||
public async Task<string> RunPerformanceTestAsync()
|
||
{
|
||
if (OrdersGrid.Items.Count == 0) return "没有可测试的数据。";
|
||
var viewer = FindVisual<ScrollViewer>(OrdersGrid).First();
|
||
var offset = viewer.VerticalOffset;
|
||
var selected = ((DataGrid)OrdersGrid).SelectedItems.Cast<object>().ToArray();
|
||
var samples = new List<double>();
|
||
try
|
||
{
|
||
OrdersGrid.UnselectAll();
|
||
OrdersGrid.ScrollIntoView(OrdersGrid.Items[0]);
|
||
viewer.ScrollToHorizontalOffset(0);
|
||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||
for (var i = 0; i < 60; i++)
|
||
{
|
||
var watch = Stopwatch.StartNew();
|
||
viewer.ScrollToVerticalOffset(viewer.ScrollableHeight * ((i * 17) % 60) / 59d);
|
||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||
samples.Add(watch.Elapsed.TotalMilliseconds);
|
||
}
|
||
var selectWatch = Stopwatch.StartNew();
|
||
OrdersGrid.SelectAll();
|
||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||
var selectMs = selectWatch.Elapsed.TotalMilliseconds;
|
||
selectWatch.Restart(); OrdersGrid.UnselectAll();
|
||
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
|
||
var clearMs = selectWatch.Elapsed.TotalMilliseconds;
|
||
var realized = FindVisual<DataGridRow>(OrdersGrid).Count();
|
||
samples.Sort();
|
||
return $"{OrdersGrid.Items.Count:N0} 行 / 9 列 | 滚动与布局:均值 {samples.Average():N1} ms,P95 {samples[56]:N1} ms,最大 {samples[^1]:N1} ms | 全选 {selectMs:N0} ms / 取消 {clearMs:N0} ms | 已生成行 {realized} | 进程内存 {Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024} MB(不等同帧率)";
|
||
}
|
||
finally
|
||
{
|
||
OrdersGrid.RestoreSelection(selected);
|
||
viewer.ScrollToVerticalOffset(offset);
|
||
}
|
||
}
|
||
|
||
private void SetBusy(bool busy)
|
||
{
|
||
LoadButton.IsEnabled = BenchmarkButton.IsEnabled = ImageBenchmarkButton.IsEnabled = DataSize.IsEnabled = ImageMode.IsEnabled = !busy;
|
||
OrdersGrid.IsEnabled = !busy;
|
||
}
|
||
|
||
private static IEnumerable<T> FindVisual<T>(DependencyObject parent) where T : DependencyObject
|
||
{
|
||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||
{
|
||
var child = VisualTreeHelper.GetChild(parent, i);
|
||
if (child is T match) yield return match;
|
||
foreach (var descendant in FindVisual<T>(child)) yield return descendant;
|
||
}
|
||
}
|
||
}
|