ObservableCollection, созданная в пуле потоков. Поток не фильтруется.
Приложение, над которым я работаю, основано на WPF и MVVMLightToolkit. Я знаю, что не предоставляю вам mcve, но все приложение действительно сложное, и трудно привести такой пример. Я надеюсь, что кто-то сможет помочь с полной картиной.
В этом приложении одно действие занимает много времени (инициализация), поэтому я запускаю его в задаче, чтобы не заморозить интерфейс:
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
метод, пользовательский интерфейс заморозить. Делая это, насколько я понимаю, ActualHeavyAction
запускается не в потоке пользовательского интерфейса, а в потоке пула потоков (исправьте меня, если я ошибаюсь).
Среди прочего ActualHeavyAction
инициализирует ObservableCollection
что мне нужно отфильтровать в отношении некоторых пользовательских входных данных (в следующем классе, свойство 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
}
}
До этого у меня нет никаких проблем. Проблема возникает позже, когда в потоке пользовательского интерфейса выполняется другое действие, изменяющее эту коллекцию. Я получаю рецидив:
Этот тип CollectionView не поддерживает изменения в его SourceCollection из потока, отличного от потока Dispatcher
Чтобы исправить это, я пытаюсь добавить.NET 4.5 EnableCollectionSynchronization
особенность:
BindingOperations.EnableCollectionSynchronization(this.MyCollection, lockObject); // lockObject is a static new object() defined in the SubViewModel class
Я пытаюсь добавить это сразу после и непосредственно перед звонком: CollectionViewSource.GetDefaultView
При этом я не получаю исключение при изменении MyCollection
, но зовет Refresh()
на CollectionView
не запускается MyFilter
метод (точно такой же код работает на других ViewModels, которые не инициализируются в потоке пула потоков).
Ты хоть представляешь, что не так с моим кодом?
1 ответ
BindingOperations.EnableCollectionSynchronization
метод должен быть вызван в потоке пользовательского интерфейса. Так что вам нужно создать ObservableCollection
и вызовите этот метод в потоке пользовательского интерфейса, прежде чем пытаться получить доступ к коллекции из фонового потока.
Но единственный метод, который должен вызываться в фоновом потоке в вашем ActualHeavyAction()
это GetCollectionAsync()
метод.
Как только задача, вызывающая этот метод, будет завершена, вы можете создать ObservableCollection
и примените фильтр обратно к потоку пользовательского интерфейса. Или только что вернул уже отфильтрованный список из задачи.
Фильтрация ICollectionView
с использованием Filter
Свойство является гибкой, но довольно медленной операцией, поэтому, если ваша исходная коллекция содержит много элементов, это может быть не лучшим вариантом для реализации фильтрации.