Reputation: 1724
I've looked at dozens of different questions related to this issue and everyone seems to recommend what I'm already doing. I'm trying to figure what I'm doing wrong.
Here's the code, it's really simple:
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
is a WrapPanel
.
<WrapPanel Name="Others" Orientation="Horizontal" />
based on the other threads I've seen, the UI thread should remain responsive as the rectangles are being created and added to the WrapPanel. But it doesn't happen. The UI hangs.
Any ideas on what I'm doing wrong?
Upvotes: 0
Views: 1213
Reputation: 17392
After some testing, I got your above scenario working without blocking the UI.
Note that I was able to achieve this using async/await
and Tasks
. This allows the adding of children to be put into the ThreadPool
of the application, allowing work to be executed once a thread is available. During this time, the message pumping still occurs, causing the UI thread not to block.
If you wrap your WrapPanel
in a ScrollViewer
, you can see that you are still able to scroll while new Rectangles
are being added.
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));
}
Another approach would be to not use Tasks
all together, but instead, allow the Dispatcher
to switch control to process events.
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();
}
}
Upvotes: 3
Reputation: 70652
The new thread isn't really accomplishing anything useful. Because the thread does nothing other than to immediately call BeginInvoke()
, all of the real work just winds up back on the dispatcher thread, where it blocks that thread until it's done.
You could, as one comment suggests refactor the loop so that the loop itself is in the thread, while the actual UI operations are not. That might look something like this:
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();
But I'm not sure that's really going to do what you want. It will queue up 50,000 separate invocations on the UI thread, all of which will definitely impede that thread's ability to handle other window messages, if not completely block any other work until they are all complete. The net effect will be very similar, if not identical, to what you're seeing now.
The other issue is that, this is WPF and you are apparently trying to create UI in code-behind.
It's not really clear how successful you're going to be getting 50,000 different Rectangle
objects populated without some type of delay perceivable by the user. But you should definitely consider creating a view model type to represent the actual Rectangle
objects, store them in an ObservableCollection<T>
, or even just a plain List<T>
, populate the collection in the background, and let WPF deal with creating the necessary Rectangle
objects through a DataTemplate
and an appropriate binding.
If you use that approach, using a List<T>
you would just create the whole list, and then update a property exposing the list, which would avoid potential cross-thread issues when the collection is updated, as well as the per-rectangle cross-thread invocation cost.
Upvotes: 1