尝试使用 Application.Current.Dispatcher.BeginInvoke 更新控件时,UI 线程冻



我已经看了几十个与这个问题相关的不同问题,每个人似乎都在推荐我已经在做的事情。我试图弄清楚我做错了什么。

这是代码,非常简单:

new Thread(() =>
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
for (int i = 0; i < 50000; i++)
{
Rectangle rectangle = new Rectangle()
{
Fill = new SolidColorBrush(Colors.Gray),
Width = 200,
Height = 290,
Margin = new Thickness(5, 0, 5, 5)
};
Others.Children.Add(rectangle);
}
}));
}).Start();

Others是一个WrapPanel.

<WrapPanel Name="Others" Orientation="Horizontal" />

根据我看到的其他线程,UI 线程应该在创建矩形并将其添加到 WrapPanel 时保持响应。但它不会发生。用户界面挂起。

对我做错了什么有什么想法吗?

经过一些测试,我让你的上述场景在不阻塞 UI 的情况下工作。

请注意,我能够使用async/awaitTasks来实现这一点。这允许将子项添加到应用程序的ThreadPool中,从而允许在线程可用后执行工作。在此期间,消息泵取仍然发生,导致 UI 线程不阻塞。

如果将WrapPanel包裹在ScrollViewer中,您可以看到在添加新Rectangles时仍可滚动。

private async void OnMainWindowLoaded(object sender, RoutedEventArgs e)
{
for (var i = 0; i < 50000; i++)
{
var rect = new Rectangle
{
Fill = new SolidColorBrush(Colors.Gray),
Width = 200,
Height = 290,
Margin = new Thickness(5,0,5,5)
};
await Task.Run(async ()=> await AddChildAsync(rect));
}
}
private async Task AddChildAsync(Rectangle rect)
{
await Application.Current.Dispatcher.InvokeAsync(()=> Others.Children.Add(rect));
}

另一种方法是不一起使用Tasks,而是允许Dispatcher将控制权切换到处理事件。

private async void OnMainWindowLoaded(object sender, RoutedEventArgs e)
{
for (var i = 0; i < 50000; i++)
{
var rect = new Rectangle
{
Fill = new SolidColorBrush(Colors.Gray),
Width = 200,
Height = 290,
Margin = new Thickness(5,0,5,5)
};
Others.Children.Add(rect);
await System.Windows.Threading.Dispatcher.Yield();
}
}

新线程并没有真正完成任何有用的事情。因为线程除了立即调用BeginInvoke()之外什么都不做,所以所有真正的工作都只是回到调度程序线程上,在那里它会阻塞该线程直到完成。

正如一条评论建议的那样,您可以重构循环,以便循环本身在线程中,而实际的 UI 操作不在线程中。这可能看起来像这样:

new Thread(() =>
{
for (int i = 0; i < 50000; i++)
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
Rectangle rectangle = new Rectangle()
{
Fill = new SolidColorBrush(Colors.Gray),
Width = 200,
Height = 290,
Margin = new Thickness(5, 0, 5, 5)
};
Others.Children.Add(rectangle);
}));
}
}).Start();

但我不确定这真的会做你想要的。它将在 UI 线程上排队 50,000 个单独的调用,所有这些调用肯定会阻碍该线程处理其他窗口消息的能力,如果不是完全阻止任何其他工作,直到它们全部完成。净效应将与您现在看到的非常相似,如果不是完全相同的话。

另一个问题是,这是 WPF,您显然正在尝试在代码隐藏中创建 UI。

目前还不清楚您将如何成功地填充 50,000 个不同的Rectangle对象,而没有用户可感知的某种类型的延迟。但是,您绝对应该考虑创建一个视图模型类型来表示实际的Rectangle对象,将它们存储在ObservableCollection<T>中,甚至只是一个普通List<T>,在后台填充集合,并让 WPF 通过DataTemplate和适当的绑定处理创建必要的Rectangle对象。

如果使用该方法,则使用List<T>只需创建整个列表,然后更新公开列表的属性,这将避免在更新集合时出现潜在的跨线程问题,以及每个矩形的跨线程调用成本。

相关内容

  • 没有找到相关文章

最新更新