13 Stimmen

Asynchrone WPF-Befehle

Hinweis : Der Code in dieser Frage ist Teil von deSleeper wenn Sie die vollständige Quelle benötigen.

Eines der Dinge, die ich von den Befehlen wollte, war ein gebackenes Design für asynchrone Operationen. Ich wollte, dass die gedrückte Schaltfläche deaktiviert wird, während der Befehl ausgeführt wird, und dass sie zurückkommt, wenn er abgeschlossen ist. Ich wollte, dass die eigentliche Arbeit in einem ThreadPool-Workitem ausgeführt wird. Und schließlich wollte ich eine Möglichkeit haben, Fehler zu behandeln, die während der asynchronen Verarbeitung auftreten.

Meine Lösung war ein 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;
    }
}

Die Frage ist also: Ist das alles notwendig? Ich habe bemerkt, in asynchrone Unterstützung für Datenbindung gebaut, warum also nicht Befehlsausführung? Vielleicht ist es im Zusammenhang mit dem Parameter Frage, die meine nächste Frage ist.

0 Stimmen

Eines der Probleme hier ist, dass der normale Entwurf für CanExecute eine befehlsspezifische Logik enthält. Z.B. könnte UndoCommand prüfen, ob es einen UndoStack gibt. Die einzige Logik, die Sie hier haben, ist, ob der Befehl bereits ausgeführt wird oder nicht.

0 Stimmen

Beachten Sie, dass CanExecute virtuell ist. In diesem Muster setze ich es außer Kraft und rufe zuerst base auf, um eine befehlsspezifische Logik bereitzustellen.

4voto

nedruod Punkte 1094

Ich konnte das ursprüngliche Beispiel verfeinern und habe einige Ratschläge für alle, die in ähnliche Situationen geraten sind.

Überlegen Sie zunächst, ob BackgroundWorker den Anforderungen gerecht wird. Ich verwende AsyncCommand noch oft, um die automatische Deaktivierungsfunktion zu erhalten, aber viele Dinge könnten mit BackgroundWorker erledigt werden.

Aber durch die Umhüllung von BackgroundWorker bietet AsyncCommand befehlsähnliche Funktionalität mit asynchronem Verhalten (ich habe auch eine Blogeintrag zu diesem Thema )

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;
    }
}

0 Stimmen

Die Verwendung der BackgroundWorker-Klasse sollte für Sie in Ordnung sein, aber bedenken Sie bitte, dass diese Klasse ursprünglich für WinForms entwickelt und gedacht wurde. Warum nutzen Sie nicht die Task Library für diese Aufgabe?

0 Stimmen

Ein guter aktualisierter Vorschlag die Antwort ist jedoch ganz einfach zu der Zeit gab es die Aufgabenbibliothek noch nicht.

2 Stimmen

Im Falle von TPL würde ich den BackgroundWorker durch folgende Codezeilen ersetzen Task.Factory.StartNew(() => OnExecute(parameter)).ContinueWith(task => OnExecuteCompleted(task.Exception), TaskScheduler.FromCurrentSynchronizationContext());

1voto

Orion Adrian Punkte 18345

Wie ich bereits in Ihrer anderen Frage geantwortet habe, möchten Sie wahrscheinlich trotzdem eine synchrone Bindung herstellen und die Befehle dann asynchron starten. Auf diese Weise vermeiden Sie die Probleme, die Sie jetzt haben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X