async CollectionViewSource过滤与进度报告?



async CollectionViewSource过滤与进度报告?

我正在修改CollectionViewSource上的搜索,从同步到异步,因为集合的典型大小已经大量增加。原因是为了给用户提供进度报告。问题是我没有看到在过滤事件处理程序中引用Progress<int>对象的方法。下面的例子有一个工作异步方法的进度报告和Filter方法的框架,显示了我的困境。我不知道如何在CollectionViewSource上实现Filter操作的ProgressBar

<Window x:Class="testProgressFilter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:testProgressFilter"
mc:Ignorable="d"
Title="MainWindow" Height="400" Width="500">
<Window.Resources>
<local:VM x:Key="VM" />
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource VM}"/>
</Window.DataContext>
<Grid>
<StackPanel Margin="30">
<StackPanel Orientation="Horizontal" >
<ProgressBar Minimum="{Binding ProgressBarMinimum}" Maximum="{Binding ProgressBarMaximum}" Value="{Binding ProgressBarValue}"
Height="30" Width="300"/>
<Button Margin="20,0,0,0" Height="30" Width="75" Command="{Binding CancelCommand}" Content="Cancel"/>
</StackPanel>
<Button Margin="0,30,0,0" HorizontalAlignment="Center" Height="30" Width="125" Command="{Binding FilterCommand}" 
Content="Do a filter" IsEnabled="{Binding ButtonsAreEnabled}"/>
<Button Margin="0,30,0,0" HorizontalAlignment="Center" Height="30" Width="125" Command="{Binding AltCommand}" 
Content="Do something else" IsEnabled="{Binding ButtonsAreEnabled}"/>
</StackPanel>
</Grid>
</Window>

View Model

using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
using custAssembliesx64;
namespace testProgressFilter
{
internal class VM : Notifier
{
private int progressBarMinimum = 0;
public int ProgressBarMinimum
{
get { return progressBarMinimum; }
set
{
if (progressBarMinimum != value)
{
progressBarMinimum = value;
OnPropertyChanged("ProgressBarMinimum");
}
}
}
private int progressBarMaximum = 100;
public int ProgressBarMaximum
{
get { return progressBarMaximum; }
set
{
if (progressBarMaximum != value)
{
progressBarMaximum = value;
OnPropertyChanged("ProgressBarMaximum");
}
}
}
private int progressBarValue;
public int ProgressBarValue
{
get { return progressBarValue; }
set
{
if (progressBarValue != value)
{
progressBarValue = value;
OnPropertyChanged("ProgressBarValue");
}
}
}
private bool buttonsAreEnabled = true;
public bool ButtonsAreEnabled
{
get { return buttonsAreEnabled; }
set
{
if (buttonsAreEnabled != value)
{
buttonsAreEnabled = value;
OnPropertyChanged("ButtonsAreEnabled");
}
}
}
internal static bool IsCancelled = false;
ObservableCollection<int> ints = new ObservableCollection<int>();
CollectionViewSource CollectionViewSource = new CollectionViewSource();
const int maxvals = 2000;
const int halfMaxvals = maxvals / 2;
int ItemsCount;
int ItemsCountPercent;
int prevItemsCountPercent;
const float cent = (float)100 / (float)maxvals;
public VM()
{
CollectionViewSource.Source = ints;
// populate the collection
for (int i = 0; i < maxvals; i++)
{
ints.Add(i);
}
}
public ICommand FilterCommand => new RelayCommand(DoFilter);
private async void DoFilter(object parm)
{
// filter the collection, showing progress
ButtonsAreEnabled = false;
int totalItems = maxvals;
var progress = new Progress<int>(percent => { ProgressBarValue = percent; });
await Task.Run(() => DoFilterTask(totalItems, progress));
IsCancelled = false;
ButtonsAreEnabled = true;
CollectionViewSource.Filter -= FilterCriteria;
}
private void DoFilterTask(int totalItems, IProgress<int> progress)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
CollectionViewSource.Filter += FilterCriteria;
}); 
}
private void FilterCriteria(object sender, FilterEventArgs e)
{
if (IsCancelled) return;
int j = (int)e.Item;
// REPORT PROGRESS HERE!
if (j < halfMaxvals)
{
e.Accepted = false;
}
else
{
e.Accepted = true;
}
}
public ICommand AltCommand => new RelayCommand(DoAlt);
private async void DoAlt(object parm)
{
ButtonsAreEnabled = false;
int totalItems = maxvals;
ProgressBarMinimum = 0;
ProgressBarMaximum = 100;
ProgressBarValue = 0;
var progress = new Progress<int>(percent => { ProgressBarValue = percent; });
await Task.Run(() => DoAltTask(totalItems, progress));
IsCancelled = false;
ButtonsAreEnabled = true;
}
private void DoAltTask(int totalItems, IProgress<int> progress)
{
//  doing something else with the collection, showing progress
ItemsCount = 0;
ItemsCountPercent = 0;
prevItemsCountPercent = 0;
double x = 1;
for (int i = 0; i < totalItems; i++)
{
if (IsCancelled) break;
x += i / 1048576;
Thread.Sleep(1);
ItemsCount++;
// report progress when > 1 additional percent is surpassed
ItemsCountPercent = (int)(cent * (float)ItemsCount);
if (ItemsCountPercent > prevItemsCountPercent)
{
prevItemsCountPercent = ItemsCountPercent;
if (progress != null)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progress.Report(ItemsCountPercent);
});
}
}
}
}

public ICommand CancelCommand => new RelayCommand(DoCancel);
private void DoCancel(object parm)
{
IsCancelled = true;
}
}
}

首先,创建一个后台线程,它什么也不做,只是将其完整的工作返回给UI线程(参见您的DoFilterTask方法)是相当无用和浪费资源。这会产生不必要的开销。

同样适用于下面的代码:

System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
progress.Report(ItemsCountPercent);
});

使用IProgress<T>的全部意义在于将进度委托的执行发送回原来的调度线程。那么为什么还要额外调用Dispatcher:Invoke呢?Dispatcher.Invoke是多余的。在这一点上,我不想提及其他没有多大意义的代码。只是必须强调IProgress<T>Dispatcher的错误使用(特别是在后台线程的上下文中)。

第二,只有在未启用UI虚拟化或不支持UI虚拟化(如普通ItemsControl)的视图中显示数据项或加载过多项时,才会遇到过滤性能差的情况。

如果我们有那么多项目,我们通常从数据库或某种数据服务中获取它们。在这种情况下,您不能过滤视图的源集合,而是查询数据源以获得过滤后的结果。例如,将工作卸载到服务器或数据库将显著提高性能。
使用支持UI虚拟化的ItemsControl(例如ListBox)是强制性的,如果您想在对源集合进行大更改(如过滤)时减少UI的不稳定感。

我也希望Thread.Sleep不在你的生产代码中。因为为什么你要人为地减慢你的算法(然后显示一个进度条)?

然而,由于CollectionViewDispatcher亲缘性(如果用作Binding.Source),您不能在与CollectionView相关联的调度线程之外的其他线程上操作它。
因此,您必须创建一个与UI线程断开连接的临时CollectionView,以便从后台线程操作它。
在你的情况下,它会更容易创建一个临时集合,你稍后分配给ListBox(或任何其他ItemsControl支持UI虚拟化)的ItemsSource属性。

下面的例子保持UI响应,同时在视图中过滤一个大的源集合:MainWindow.xaml

<ProgressBar x:Name="ProgressBar" 
Maximum="{Binding NumericItems.Count}" 
Height="8" />
<Button Content="Filter Items"
Click="Button_Click" />
<ListBox ItemsSource="{Binding NumericItems}" 
Height="500" />

MainWindow.xaml.cs

partial class MainWindow : Window
{
public ObservableCollection<int> NumericItems
{
get => (ObservableCollection<int>)GetValue(NumericItemsProperty);
set => SetValue(NumericItemsProperty, value);
}
public static readonly DependencyProperty NumericItemsProperty = DependencyProperty.Register(
"NumericItems", 
typeof(ObservableCollection<int>), 
typeof(MainWindow), 
new PropertyMetadata(default));
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.NumericItems = new ObservableCollection<int>();
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var rndGenerator = new Random();
int itemCount = 10000
for (int i = 0; i < itemCount; i++)
{
int number = rndGenerator.Next(0, itemCount);
this.NumericItems.Add(number);
}
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
var tempList = this.NumericItems.ToList();
IProgress<int> progressReporter = new Progress<int>(progressValue => this.ProgressBar.Value = progressValue);
int processedItemCount = 0;
await Task.Run(() =>
{
for (int index = tempList.Count - 1; index >= 0; index--)
{
int value = tempList[index];
if (!IsItemIncluded(value))
{
tempList.RemoveAt(index);
}
progressReporter.Report(++processedItemCount);
}
});
// If the data view is not using UI virtualization, 
// this operation will cause the UI to hang until the data view is initialized.
this.NumericItems = new ObservableCollection<int>(tempList);
}
private bool IsItemIncluded(int item) => item % 2 == 0;
}

第一个解决方案真的很好,

另一个只使用调度程序的解决方案:

  1. 创建进度条中加载的事件定义XAML(称为ProgressBarLoaded)

在视频模型中使用

public void ProgressBarLoaded(object sender, RoutedEventArgs e)
{
bar = sender as ProgressBar;
ItemsCount = 0;
ItemsCountPercent = 0;
}

和在FilterCriteria中更新值:

private void FilterCriteria(object sender, FilterEventArgs e)
{
if (IsCancelled) return;
int j = (int)e.Item;
if (j < halfMaxvals)
{
e.Accepted = false;
}
else
{
e.Accepted = true;
}
ItemsCountPercent = ++ItemsCount * 100 / maxvals;
bar.Dispatcher.Invoke(() => bar.Value = ItemsCountPercent, DispatcherPriority.Background);
}

不需要定义绑定到ProgressBarValue..

在这个示例中,我在筛选集合

期间使用进度条

最新更新