在线程池线程上创建的 ObservableCollection 不过滤

ObservableCollection created on a Thread Pool Thread does not filter

我正在开发的应用程序基于 WPF 和 MVVMLightToolkit。我知道我不会为您提供 mcve,但整个应用程序非常复杂,很难提供这样的示例。我希望有人能够帮助整个画面。

在那个应用程序中,一个动作需要很多时间(初始化的东西),所以我 运行 它在一个任务中不冻结 UI:

public class MainViewModel : ViewModelBase
{
    public ICommand HeavyActionCommand {get; private set;}
    public MainViewModel()
    {
        this.HeavyActionCommand = new RelayCommand(this.HeavyAction);
    }
    private async void HeavyAction()
    {
        var subViewModel = new SubViewModel();
        await Task.Run(async () => await subViewModel.ActualHeavyAction());
    }
}

如果我不将 ActualHeavyAction 包装在 Task.Run 方法中,UI 会冻结。这样做,据我所知,ActualHeavyAction 不是 UI 线程上的 运行,而是线程池线程上的 运行(如果我错了请纠正我)。

除其他事项外,ActualHeavyAction 初始化了一个 ObservableCollection,我需要针对某些用户实时输入进行过滤(在下面的 class、属性 UserInput 绑定到 TextBox)。我有类似的东西:

public class SubViewModel: ViewModelBase
{
    private _userInput;
    public string UserInput
    {
        get { return _userInput; }
        set
        {
            if (_userInput != value)
            {
                _userInput = value;
                this.RaisePropertyChanged();

                // Run the filter on the collection when the user enters new inputs
                CollectionViewSource.GetDefaultView(this.MyCollection).Refresh();
            }
        }
    }

    public ObservableCollection MyCollection {get; private set;}

    public async Task ActualHeavyAction()
    {
        /// lots of heavy stuff

        var myCollection = await _context.Objects.GetCollectionAsync();
        this.MyCollection = new ObservableCollection(myCollection);

        this.RaisePropertyChanged(nameof(this.MyCollection));

        CollectionViewSource.GetDefaultView(this.MyCollection).Filter = MyFilter;

        /// some other heavy stuff
    }

    public bool MyFilter(object obj)
    {
        // Blah blah blah
    }
}

到这里为止,我没有遇到任何麻烦。稍后会出现问题,当 UI 线程上的另一个操作 运行 修改该集合时。我得到了经常性的:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread

要修复它,我尝试添加 .NET 4.5 EnableCollectionSynchronization 功能:

BindingOperations.EnableCollectionSynchronization(this.MyCollection, lockObject); // lockObject is a static new object() defined in the SubViewModel class

我尝试在调用之后和之前添加它:CollectionViewSource.GetDefaultView

这样做,当我修改 MyCollection 时我没有得到异常,但是在 CollectionView 上调用 Refresh() 不会 运行 MyFilter方法(完全相同的代码适用于未在线程池线程上初始化的其他 ViewModel)。

你知道我的代码有什么问题吗?

应该在 UI 线程上调用 BindingOperations.EnableCollectionSynchronization 方法。因此,您需要创建 ObservableCollection 并在 UI 线程 上调用此方法,然后 您尝试从后台线程访问集合。

但是 ActualHeavyAction() 中唯一应该在后台线程上调用的方法是 GetCollectionAsync() 方法。

调用此方法的任务完成后,您可以创建 ObservableCollection 并将过滤器应用回 UI 线程。或者只是从任务中返回一个已经过滤的列表。

使用 Filter 属性 过滤 ICollectionView 是一种灵活但相当慢的操作,因此如果您的源集合包含大量项目,这可能不是最佳选择实施过滤。