C# 如何使用异步命令MVVM WPF

C# 如何使用异步命令MVVM WPF,c#,.net,wpf,mvvm,C#,.net,Wpf,Mvvm,因此,我最近开始与MVVM合作,最终开始了解这个概念。 我似乎偶然发现了一个关于命令的小问题。 我创建了一个有按钮和文本框的项目,计划是单击按钮,然后生成一些文本到文本框。 当然,使用MVVM。 所以我设法让它工作,唯一的问题是我的按钮被绑定到一个命令,而不是什么,当点击按钮时,我的UI冻结了,这有明显的原因,但这让我思考。为了防止UI死锁,您通常会使用async&await,但这样一来,它开始抱怨Execute()中没有等待的方法在该命令中。 处理这种情况下的UI死锁的正确方法是什么? 我要发

因此,我最近开始与MVVM合作,最终开始了解这个概念。 我似乎偶然发现了一个关于命令的小问题。 我创建了一个有按钮和文本框的项目,计划是单击按钮,然后生成一些文本到文本框。 当然,使用MVVM。 所以我设法让它工作,唯一的问题是我的按钮被绑定到一个命令,而不是什么,当点击按钮时,我的UI冻结了,这有明显的原因,但这让我思考。为了防止UI死锁,您通常会使用async&await,但这样一来,它开始抱怨
Execute()中没有等待的方法在该命令中。
处理这种情况下的UI死锁的正确方法是什么?
我要发出异步命令还是?
另外,如果我需要一个AsyncCommand,创建它的最佳方法是什么

看法

视图模型

public class ViewModel : INotifyPropertyChanged
    {
        private ProductService _productService;
        private ProductModel _myProduct;
        public AddProductCommand AddProductCommand { get; set; }



        public ViewModel()
        {
            _productService = new ProductService();
            _myProduct = new ProductModel();
            AddProductCommand = new AddProductCommand(this);

        }

        //Bind a button to a command that invokes this method.
        public void FillDescription()
        {
            _myProduct.ProductName = _productService.GetProductName();
        }

        public ProductModel MyProduct
        {
            get { return _myProduct; }
            set
            {
                _myProduct = value;
                OnPropertyChanged("MyProduct");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
服务

public class ProductService
{
    public ProductService()
    {

    }

    public string GetProductName()
    {
        var web = new HtmlWeb();
        var doc = web.Load("https://shop.supersimpleonline.com/products/baby-shark-official-plush");
        var title = doc.DocumentNode.SelectNodes("//h1[@itemprop = 'name']").FirstOrDefault(x => x != null).InnerText;
        return title;
    }
}
命令

public class AddProductCommand : ICommand
{
    public ViewModel ViewModel { get; set; }
    public AddProductCommand(ViewModel viewModel)
    {
        ViewModel = viewModel;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        ViewModel.FillDescription();
    }

    public event EventHandler CanExecuteChanged;
}

这里有一个中继命令,它允许您执行异步工作,并防止命令多次触发/还显示按钮禁用等。它非常简单

public class AsyncRelayCommand : ICommand
{
    public Func<object, Task> ExecuteFunction { get; }
    public Predicate<object> CanExecutePredicate { get; }
    public event EventHandler CanExecuteChanged;
    public void UpdateCanExecute() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    public bool IsWorking { get; private set; }

    public AsyncRelayCommand(Func<object, Task> executeFunction) : this(executeFunction, (obj) => true) { }
    public AsyncRelayCommand(Func<object, Task> executeFunction, Predicate<object> canExecutePredicate)
    {
        ExecuteFunction = executeFunction;
        CanExecutePredicate = canExecutePredicate;
    }

    public bool CanExecute(object parameter) => !IsWorking && (CanExecutePredicate?.Invoke(parameter) ?? true);
    public async void Execute(object parameter)
    {
        IsWorking = true;
        UpdateCanExecute();

        await ExecuteFunction(parameter);

        IsWorking = false;
        UpdateCanExecute();
    }
}

希望这有帮助:)

创建一个通用命令类,该类接受执行命令时要调用的
操作:

public class DelegateCommand : System.Windows.Input.ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    {
        _execute = execute;
    }

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
            return true;

        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged;
}

如果在execute方法中调用Task.Run(),会发生什么?我还没有检查过,但我想应该检查一下work@mahlatse这不好,只是触发并忘记了任务,我可能会丢失一个异常,或者有人可能会再次按下你的按钮。。突然竞争条件对于异常部分,可以使用continuewith并获取异常对象为什么构造函数为空?第一个是。。公共异步无效不是坏习惯吗?我想你应该总是回一封信Task@MarkDenom:构造函数不应为空,
FillDescription
不应为公共。我把这个修好了。
FillDescription
方法标记为
async
的唯一原因是您可以在其中使用
wait
关键字。由于
ICommand
Execute
方法不是异步的,因此在框架执行命令时不会等待它。
public class AsyncRelayCommand : ICommand
{
    public Func<object, Task> ExecuteFunction { get; }
    public Predicate<object> CanExecutePredicate { get; }
    public event EventHandler CanExecuteChanged;
    public void UpdateCanExecute() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    public bool IsWorking { get; private set; }

    public AsyncRelayCommand(Func<object, Task> executeFunction) : this(executeFunction, (obj) => true) { }
    public AsyncRelayCommand(Func<object, Task> executeFunction, Predicate<object> canExecutePredicate)
    {
        ExecuteFunction = executeFunction;
        CanExecutePredicate = canExecutePredicate;
    }

    public bool CanExecute(object parameter) => !IsWorking && (CanExecutePredicate?.Invoke(parameter) ?? true);
    public async void Execute(object parameter)
    {
        IsWorking = true;
        UpdateCanExecute();

        await ExecuteFunction(parameter);

        IsWorking = false;
        UpdateCanExecute();
    }
}
public class ViewModel : INotifyPropertyChanged
{
    private bool asyncCommandWorking;
    public event PropertyChangedEventHandler PropertyChanged;
    public void Notify([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    public ViewModel()
    {
        AsyncCommand = new AsyncRelayCommand(Execute, CanExecute);
    }

    private Task Execute(object obj)
    {
        return Task.Run(() =>
        {
            // do some work...
        });
    }

    private bool CanExecute(object obj)
    {
        AsyncCommandWorking = AsyncCommand.IsWorking;
        // process other can execute logic.
        // return the result of CanExecute or not
    }

    public AsyncRelayCommand AsyncCommand { get; }
    public bool AsyncCommandWorking
    {
        get => asyncCommandWorking;
        private set
        {
            asyncCommandWorking = value;
            Notify();
        }
    }
}
public class DelegateCommand : System.Windows.Input.ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public DelegateCommand(Action<object> execute)
        : this(execute, null)
    {
        _execute = execute;
    }

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (_canExecute == null)
            return true;

        return _canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    public event EventHandler CanExecuteChanged;
}
public class ViewModel : INotifyPropertyChanged
{
    public DelegateCommand AddProductCommand { get; set; }

    public ViewModel()
    {
        _productService = new ProductService();
        _myProduct = new ProductModel();
        AddProductCommand = new DelegateCommand(FillDescription);
    }

    async void FillDescription(object _)
    {
        try
        {
            await Task.Run(() => _myProduct.ProductName = _productService.GetProductName());
        }
        catch(Exception)
        {
            //...
        }
    }
}