Асинхронные команды WPF

Примечание. Код в этом вопросе является частью deSleeper, если вы хотите получить полный источник.

Одна из вещей, которые я хотел получить от команд, - это испеченный дизайн для асинхронных операций. Я хотел, чтобы кнопка нажата для отключения во время выполнения команды, и вернитесь, когда закончите. Я хотел, чтобы фактическая работа выполнялась в рабочем элементе ThreadPool. И, наконец, мне нужен способ обработки любых ошибок, которые произошли во время асинхронной обработки.

Мое решение было AsyncCommand:

public abstract class AsyncCommand : ICommand
{
    public event EventHandler CanExecuteChanged;
    public event EventHandler ExecutionStarting;
    public event EventHandler<AsyncCommandCompleteEventArgs> ExecutionComplete;

    public abstract string Text { get; }
    private bool _isExecuting;
    public bool IsExecuting
    {
        get { return _isExecuting; }
        private set
        {
            _isExecuting = value;
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }
    }

    protected abstract void OnExecute(object parameter);

    public void Execute(object parameter)
    {   
        try
        {
            IsExecuting = true;
            if (ExecutionStarting != null)
                ExecutionStarting(this, EventArgs.Empty);

            var dispatcher = Dispatcher.CurrentDispatcher;
            ThreadPool.QueueUserWorkItem(
                obj =>
                {
                    try
                    {
                        OnExecute(parameter);
                        if (ExecutionComplete != null)
                            dispatcher.Invoke(DispatcherPriority.Normal, 
                                ExecutionComplete, this, 
                                new AsyncCommandCompleteEventArgs(null));
                    }
                    catch (Exception ex)
                    {
                        if (ExecutionComplete != null)
                            dispatcher.Invoke(DispatcherPriority.Normal, 
                                ExecutionComplete, this, 
                                new AsyncCommandCompleteEventArgs(ex));
                    }
                    finally
                    {
                        dispatcher.Invoke(DispatcherPriority.Normal, 
                            new Action(() => IsExecuting = false));
                    }
                });
        }
        catch (Exception ex)
        {
            IsExecuting = false;
            if (ExecutionComplete != null)
                ExecutionComplete(this, new AsyncCommandCompleteEventArgs(ex));
        }
    }

    public virtual bool CanExecute(object parameter)
    {
        return !IsExecuting;
    }
}

так что вопрос: все это необходимо? Я заметил встроенную асинхронную поддержку привязки данных, так почему бы не выполнить команду? Возможно, это связано с параметром вопроса, который является моим следующим вопросом.

Ответы

Ответ 1

Мне удалось усовершенствовать исходный образец и дать совет кому-либо, работающему в подобных ситуациях.

Сначала подумайте, удовлетворит ли BackgroundWorker потребности. Я по-прежнему часто использую AsyncCommand, чтобы получить функцию автоматического отключения, но если с BackgroundWorker можно сделать много вещей.

Но, обернув BackgroundWorker, AsyncCommand предоставляет команду как функциональность с асинхронным поведением (у меня также есть запись в этом разделе)

public abstract class AsyncCommand : ICommand
{
    public event EventHandler CanExecuteChanged;
    public event EventHandler RunWorkerStarting;
    public event RunWorkerCompletedEventHandler RunWorkerCompleted;

    public abstract string Text { get; }
    private bool _isExecuting;
    public bool IsExecuting
    {
        get { return _isExecuting; }
        private set
        {
            _isExecuting = value;
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }
    }

    protected abstract void OnExecute(object parameter);

    public void Execute(object parameter)
    {   
        try
        {   
            onRunWorkerStarting();

            var worker = new BackgroundWorker();
            worker.DoWork += ((sender, e) => OnExecute(e.Argument));
            worker.RunWorkerCompleted += ((sender, e) => onRunWorkerCompleted(e));
            worker.RunWorkerAsync(parameter);
        }
        catch (Exception ex)
        {
            onRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
        }
    }

    private void onRunWorkerStarting()
    {
        IsExecuting = true;
        if (RunWorkerStarting != null)
            RunWorkerStarting(this, EventArgs.Empty);
    }

    private void onRunWorkerCompleted(RunWorkerCompletedEventArgs e)
    {
        IsExecuting = false;
        if (RunWorkerCompleted != null)
            RunWorkerCompleted(this, e);
    }

    public virtual bool CanExecute(object parameter)
    {
        return !IsExecuting;
    }
}

Ответ 2

Как я уже ответил в другом вопросе, вы, вероятно, все же хотите привязываться к этому синхронно, а затем запускать команды асинхронно. Таким образом, вы избегаете проблем, с которыми вы сейчас сталкиваетесь.