当您在 MVVM 中使用 Async 命令时,主要问题是 NotSupportedException 在修改 ObservableCollection 时。如果您注意并发性,其余所有内容都不会引起任何问题。
这是一个代表ObservableCollection 的类,供多个线程使用,它将所有操作转发到构造它的线程的SynchronizationsContext。
用它代替ObservableCollection(不是我的,从GitHub抓来的)
public class AsyncObservableCollection<T> : ObservableCollection<T>
{
private readonly SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public AsyncObservableCollection() : base() { }
public AsyncObservableCollection(IEnumerable<T> collection) : base(collection) { }
public AsyncObservableCollection(List<T> list) : base(list) { }
private void ExecuteOnSyncContext(Action action)
{
if (SynchronizationContext.Current == _synchronizationContext)
action();
else
_synchronizationContext.Send(_ => action(), null);
}
protected override void InsertItem(int index, T item) => ExecuteOnSyncContext(() => base.InsertItem(index, item));
protected override void RemoveItem(int index) => ExecuteOnSyncContext(() => base.RemoveItem(index));
protected override void SetItem(int index, T item) => ExecuteOnSyncContext(() => base.SetItem(index, item));
protected override void MoveItem(int oldIndex, int newIndex) => ExecuteOnSyncContext(() => base.MoveItem(oldIndex, newIndex));
protected override void ClearItems() => ExecuteOnSyncContext(() => base.ClearItems());
}
我在 StackOverflow (Russian Community) 的帮助下创建了这个 AsyncRelayCommand 类。它不会冻结任何东西。
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object param);
}
public class AsyncRelayCommand : IAsyncCommand
{
private bool _isExecuting;
private readonly SynchronizationContext _context;
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public AsyncRelayCommand(Action<object> execute, Predicate<object> canExecute = null)
=> (_execute, _canExecute, _context) = (execute, canExecute, SynchronizationContext.Current);
private void InvalidateRequerySuggested()
{
if (_context.Equals(SynchronizationContext.Current))
CommandManager.InvalidateRequerySuggested();
else
_context.Send(_ => CommandManager.InvalidateRequerySuggested(), null);
}
public bool CanExecute(object parameter) => !_isExecuting && (_canExecute == null || _canExecute(parameter));
public async Task ExecuteAsync(object parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
InvalidateRequerySuggested();
await Task.Run(() => _execute(parameter));
}
finally
{
_isExecuting = false;
InvalidateRequerySuggested();
}
}
}
public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}
与this article 中的常规RelayCommand 类一样使用。
private IAsyncCommand _myAsyncCommand;
// "lazy" instantiation with single instance
public IAsyncCommand MyAsyncCommand => _myAsyncCommand ?? (_myAsyncCommand = new AsyncRelayCommand(parameter =>
{
}));
<Button Content="Click me!" Command="{Binding MyAsyncCommand}" />
还支持CommandParameter。
有了这个,您不需要将收集更改调用推送到 UI 线程中,就像在通常的同步代码中一样使用它。并且Thread.Sleep() 或命令中的繁重工作不会冻结 UI,因为它将在单独的线程上运行。
与您的代码一起使用
private IAsyncCommand _appearingCommand;
public AsyncObservableCollection<CarViewModel> cars; // are you sure that it must be public?
public HomeDetailPageViewModel(INavigationService navigationService)
:base(navigationService)
{
this.cars = new AsyncObservableCollection<CarViewModel>();
}
public AsyncObservableCollection<CarViewModel> Cars
{
get => this.cars;
}
public IAsyncCommand AppearingCommand => _appearingCommand ?? (_appearingCommand = new AsyncRelayCommand(parameter =>
{
// edit: fixed regarding to the accepted answer
List<Car> carsFromEndpoint = CarEndpoint.GetAll(client.Context).ToList();
foreach (var carFromEndpoint in carsFromEndpoint)
this.Cars.Add(new CarViewModel
{
Manufacturer = carFromEndpoint.Code,
Model = carFromEndpoint.Name,
Price = carFromEndpoint.Price,
Year = carFromEndpoint.Year
});
}));