Files
29WxOrder/Main.Test/MainWindow.xaml.cs
T
2026-10-22 13:42:04 +08:00

154 lines
7.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
}