c# - 使用异步命令 MVVM WPF 卡住并且 UI 被冻结
问题描述
我使用了以下视图模型MainWindow.xaml
,该视图模型称为MainViewModel
:
public abstract class AbstractPropNotifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public sealed class MainViewModel : AbstractPropNotifier
{
private bool _editEnabled;
private bool _deleteEnabled;
private ICommand _editCommand;
private ICommand _deleteCommand;
private IRssViewModel _selectedIrssi;
private IAsyncCommand _addCommand;
private readonly Dispatcher _dispatcher;
public MainViewModel(Dispatcher dispatcher)
{
_dispatcher = dispatcher;
IrssItems = new ObservableCollection<IRssViewModel>();
Log = new ObservableCollection<string>();
EditEnabled = false;
DeleteEnabled = false;
EditCommand = new RelayCommand(c => EditItem(), p => EditEnabled);
DeleteCommand = new RelayCommand(DeleteItems, p => DeleteEnabled);
AddCommand = new AsyncCommand(AddItem, () => true);
}
public ObservableCollection<IRssViewModel> IrssItems { get; set; }
public IRssViewModel SelectedIrssi
{
get
{
return _selectedIrssi;
}
set
{
_selectedIrssi = value;
OnPropertyChanged(nameof(SelectedIrssi));
EditEnabled = DeleteEnabled = true;
}
}
public ObservableCollection<string> Log { get; set; }
public bool EditEnabled
{
get
{
return _editEnabled;
}
set
{
_editEnabled = value || SelectedIrssi != null;
OnPropertyChanged(nameof(EditEnabled));
}
}
public bool DeleteEnabled
{
get
{
return _deleteEnabled;
}
set
{
_deleteEnabled = value || SelectedIrssi != null;
OnPropertyChanged(nameof(DeleteEnabled));
}
}
public ICommand EditCommand
{
get
{
return _editCommand;
}
set
{
_editCommand = value;
}
}
public ICommand DeleteCommand
{
get
{
return _deleteCommand;
}
set
{
_deleteCommand = value;
}
}
public IAsyncCommand AddCommand
{
get
{
return _addCommand;
}
set
{
_addCommand = value;
}
}
private void EditItem()
{
}
private void DeleteItems(object selectedItems)
{
var list = selectedItems as IList;
var newList = new List<IRssViewModel>(list.Cast<IRssViewModel>());
if (MessageBox.Show($"Are you sure that want to delete {newList.Count} item{(newList.Count > 1 ? "s" : "")} ?", "Deletion", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
foreach (var item in newList)
{
IrssItems.Remove(item as IRssViewModel);
}
EditEnabled = DeleteEnabled = false;
}
}
private async Task AddItem()
{
var win = new ManageIrssi("Add item");
var result = win.ShowDialog();
if (result.HasValue && result.Value)
{
foreach (var data in win.Model.Items)
{
//check stuff
IrssItems.Add(data);
await CreateConnection(data);
}
}
}
private async Task CreateConnection(IRssViewModel data)
{
await Task.Run(() =>
{
IrcManager manager = new IrcManager(new CustomLogger(), data);
manager.Build(s => _dispatcher.Invoke(() => Log.Add(s)));
data.IsConnected = true;
});
}
}
并且AsynCommand
来自https://johnthiriet.com/mvvm-going-async-with-async-command/
public class AsyncCommand : IAsyncCommand
{
public event EventHandler CanExecuteChanged;
private bool _isExecuting;
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
private readonly IErrorHandler _errorHandler;
public AsyncCommand(
Func<Task> execute,
Func<bool> canExecute = null,
IErrorHandler errorHandler = null)
{
_execute = execute;
_canExecute = canExecute;
_errorHandler = errorHandler;
}
public bool CanExecute()
{
return !_isExecuting && (_canExecute?.Invoke() ?? true);
}
public async Task ExecuteAsync()
{
if (CanExecute())
{
try
{
_isExecuting = true;
await _execute();
}
finally
{
_isExecuting = false;
}
}
RaiseCanExecuteChanged();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
#region Explicit implementations
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
ExecuteAsync().GetAwaiter().GetResult();
}
#endregion
}
我遇到的问题是,按下按钮后Add
,执行最后一行data.IsConnected = true;
,然后什么也没有发生,这意味着 UI 被冻结并且 UI 数据网格中没有添加任何项目。
我也删除了部分_dispatcher.Invoke(() => Log.Add(s)
,同样的问题,UI 冻结。
为什么 ?我的错误在哪里?似乎问题出在await CreateConnection(data)
解决方案
您的示例代码既不是可编译的也不是最小的Execute
,但我可以发现您的命令方法中的一个缺陷:
void ICommand.Execute(object parameter)
{
ExecuteAsync().GetAwaiter().GetResult();
}
调用Result
一个Task
可能的死锁是一个很大的禁忌,尤其是在 GUI 应用程序中。尝试触发Task
然后从该方法返回:
async void ICommand.Execute(object parameter)
{
await ExecuteAsync().ConfigureAwait(false);
}
推荐阅读
- javascript - 在 ReactJS 中使用多个布局的正确方法是什么
- sql-server - sql server如何从多列但不是所有列中区分数据
- excel - Excel Web 查询:将查询 URL 替换为另一个表中的值
- gatsby - 在 Netlify 上构建 gatsby 站点 - 错误:回调已被调用
- keras - 如何在 AutoEncoder 模型上绘制 ROC 曲线并计算 AUC?
- azure-devops - 带有 PAT 的 Azure devops 提要:需要哪些权限?
- javascript - 在本地运行 Angular 7 项目时出错
- typescript - Typescript / Mocha 测试框架:阻塞和等待承诺的初始化代码
- php - (已解决)一些本地工作的会话和功能突然在远程服务器上不再工作
- video - ffmpeg 在一个命令中剪切视频和刻录字幕