I have a C# WPF application using a rather noddy MVVM approach. In one of the ViewModels I would like to run a sequence of tasks sequentially but would like to run each asynchronous to the main thread. I want the granularity of being able to report progress between tasks but I don't want to block the GUI while any of the tasks is running.
Is there a standard way of achieving this, or a "best practice"?
I have implemented something that makes use of BackgroundWorker
that I feel at once happy and mildly horrified with. The code to kick the whole thing off feels especially non-C#ish. I feel there must be a better or, at least, an established way of doing this.
Many thanks 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);
开发者_StackOverflow 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; }
}
Which leads to the Perl-like syntax of:
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()
{
etc.
Have you looked at the Task Parallel Library (TPL) in .NET 4.0? This allows you to do things like this:
Task firstTask = new Task(()=>RunStepOne());
firstTask.ContinueWith(task=>()=>RunSecondStep());
firstTask.Start();
There are a ton of options for creating, continuing, and stopping tasks built on the TPL. It will certainly be worth a look.
I don't see anything wrong with your approach. And the most important thing is: it works.
精彩评论