I have a C # WPF application using a fairly innovative MVVM approach. In one of ViewModels, I would like to execute a sequence of tasks sequentially, but I would like to start each asynchronous main thread. I want the granularity to be able to report progress between tasks, but I do not want to block the graphical interface when any of the tasks is performed.
Is there a standard way to achieve this goal or โbest practiceโ?
I implemented something that uses BackgroundWorker that I immediately feel happy and a little horrified. The code, to discard all this, feels especially non-C # ish. I believe that there should be a better, or at least an established way to do this.
Thanks so much for your suggestions.
Dan
Here's the cobbled-together option:
protected void runAsyncTask(SequentialTask seqTask) { if (HasErrored) return; DoWorkEventHandler worker = (s, e) => { setTaskStartStatusMessage(seqTask.TaskMessage); ShowProgress = true; seqTask.Task((BackgroundWorker)s); }; ProgressChangedEventHandler progress = (s, e) => { if (seqTask.TaskProgress != null) seqTask.TaskProgress(e.ProgressPercentage, e.UserState); }; RunWorkerCompletedEventHandler done = null; done = (s, e) => { ShowProgress = false; if (e.Error != null) { HasErrored = true; displayTaskExceptionMessage(e.Error, seqTask.TaskMessage); } else { setTaskCompleteStatusMessage(seqTask.TaskMessage); if (seqTask.TaskComplete != null) seqTask.TaskComplete(); } ((BackgroundWorker)s).RunWorkerCompleted -= done; ((BackgroundWorker)s).DoWork -= worker; ((BackgroundWorker)s).ProgressChanged -= progress; if (seqTask.NextTask != null && (seqTask.CanExecuteNext == null ? true : seqTask.CanExecuteNext())) runAsyncTask(seqTask.NextTask); }; if (seqTask.TaskProgress != null) backgroundWorker.WorkerReportsProgress = true; backgroundWorker.DoWork += worker; backgroundWorker.RunWorkerCompleted += done; backgroundWorker.ProgressChanged += progress; backgroundWorker.RunWorkerAsync(); }
SequentialTask is just a simple set of properties:
public class SequentialTask { public Action<BackgroundWorker> Task { get; set; } public String TaskMessage { get; set; } public Func<bool> CanExecuteNext { get; set; } public Action<int, object> TaskProgress { get; set; } public Action TaskComplete { get; set; } public SequentialTask NextTask { get; set; } }
This results in syntax similar to Perl:
runAsyncTask(new SequentialTask() { Task = (x) => loadFile(), TaskMessage = "Load File", CanExecuteNext = null, NextTask = new SequentialTask() { Task = (x) => validateImport(), TaskMessage = "Validate Input Lines", TaskComplete = () => { if (!ImportIsValid) displayValidationMessages(); }, CanExecuteNext = () => ImportIsValid, NextTask = new SequentialTask() {
and etc.