I prefer using ICollectionView.Refresh()
on an arbitrary collection instead of using ObservableCollection<T>
as most of the time I know where the underlying collection changes and prefer to minimise notifications. This worked pretty well until I started to use async and noticed that it does not play nicely with ICollectionView.Refresh()
if I set ConfigureAwait(false)
. With ConfigureAwait(true)
it refreshes nicely, of course.
I understand why it does not work with ConfigureAwait(false)
: the synchronisation context is not restored and the refresh operation is called from a non-UI thread. I, however, should expect it to throw an exception but instead it fails silently and the UI is not updated.
What intrigues me is that the usual technique to marshal the call to the UI thread by Application.Current.Dispatcher.Invoke()
also fails in this case. No exception occurs but the UI does not update. Why? How can I ensure that refreshing the ICollectionView
works even where I fet to configure the synchronisation context properly?
MWE:
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace ICollectionViewRefresh;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly List<int> list = new List<int>();
public ICollectionView List => CollectionViewSource.GetDefaultView(list);
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private async void HandleButtonClick(object sender, RoutedEventArgs e)
{
list.Clear();
await Task.Run(() => {
for (int i = 0; i < 128; i++) {
list.Add(Random.Shared.Next(1, 20));
}
}).ConfigureAwait(false); // Only updates with .ConfigureAwait(true) even with MarshallingRefresh().
// List.Refresh() silently fails with .ConfigureAwait(false) so let us try to marshal it to the UI thread:
MarshallingRefresh(List);
}
private void MarshallingRefresh(ICollectionView collectionView) => Application.Current.Dispatcher.Invoke(() => collectionView.Refresh());
}
with the XAML
<Window x:Class="ICollectionViewRefresh.MainWindow"
xmlns=";
xmlns:x=";
xmlns:d=";
xmlns:mc=";
xmlns:local="clr-namespace:ICollectionViewRefresh"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Button Content="Fill list" Click="HandleButtonClick" />
</StackPanel>
<ListBox Grid.Row="1" ItemsSource="{Binding List}" />
</Grid>
</Window>
Update: Upon @Sinatr 's advice, I replaced the method call MarshallingRefresh(List)
with a direct lambda call Application.Current.Dispatcher.Invoke(() => List.Refresh());
and it does update the UI. But with a method name instead of a lambda, ie, Application.Current.Dispatcher.Invoke(List.Refresh);
, it does not update.