Mam aplikację WPF
i używam komponentu BackgroundWorker
do pobierania niektórych danych z zaplecza i wyświetlania ich w interfejsie użytkownika.Śledzenie wielu BackgroundWorkers
Ma BackgroundWorker
ma WorkerReportsProgress = true
, dzięki czemu interfejs użytkownika może być okresowo aktualizowany. Model BackgroundWorker
ma także WorkerSupportsCancellation = true
, dzięki czemu może zostać anulowany przez użytkownika. Wszystko, co działa świetnie!
Mam problemy z zaimplementowaniem trzeciego i bardziej złożonego zachowania. Zasadniczo użytkownik musi mieć możliwość rozpoczęcia nowego zadania w dowolnym momencie, w tym podczas wykonywania jednego. Jeśli aktualnie wykonywane jest zadanie i rozpoczyna się nowe, stare zadanie musi zostać oznaczone jako Aborted
. Aborted
zadania różnią się od Cancelled
tym, że Aborted
nie może dokonywać żadnych dalszych aktualizacji interfejsu użytkownika. Powinno być "anulowane cicho".
Zawinęłem BackgroundWorker
wewnątrz klasy AsyncTask
i dodano bit IsAborted
. Sprawdzanie w stosunku do bitów IsAborted
wewnątrz ProgressChanged
i RunWorkerCompleted
zapobiega dalszym aktualizacjom interfejsu użytkownika. Wspaniały!
Jednak to podejście ulega przerwaniu, ponieważ po uruchomieniu nowych zadań, CurrentTask
zostaje zastąpione nowym wystąpieniem AsyncTask
. W rezultacie trudno jest śledzić numer CurrentTask
.
Jak już wspomniano, w TODO:
, prawie tak, jak chcę poczekać, aż CurrentTask
zakończy się po przerwie przed rozpoczęciem nowego zadania. Ale wiem, że spowoduje to złe wrażenia użytkownika, ponieważ wątek interfejsu użytkownika zostanie zablokowany do czasu ukończenia starego zadania.
Czy istnieje lepszy sposób śledzenia wielu numerów AsyncTasks
, zapewniający możliwość uruchamiania nowych na żądanie, a stare są przerywane poprawnie bez dalszych aktualizacji interfejsu użytkownika? Wydaje się, że nie jest dobrym sposobem na śledzenie CurrentTask ... Czy ta TPL oferuje lepszy sposób radzenia sobie z tym, co chcę?
Oto ważniejsze fragmenty mam w mojej klasie okna:
private AsyncTask CurrentTask { get; set; }
private class AsyncTask
{
private static int Ids { get; set; }
public AsyncTask()
{
Ids = Ids + 1;
this.Id = Ids;
this.BackgroundWorker = new BackgroundWorker();
this.BackgroundWorker.WorkerReportsProgress = true;
this.BackgroundWorker.WorkerSupportsCancellation = true;
}
public int Id { get; private set; }
public BackgroundWorker BackgroundWorker { get; private set; }
public bool IsAborted { get; set; }
}
void StartNewTask()
{
if (this.CurrentTask != null && this.CurrentTask.BackgroundWorker.IsBusy)
{
AbortTask();
//TODO: should we wait for CurrentTask to finish up? this will block the UI?
}
var asyncTask = new AsyncTask();
asyncTask.BackgroundWorker.DoWork += backgroundWorker_DoWork;
asyncTask.BackgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
asyncTask.BackgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
AppendText("Starting New Task: " + asyncTask.Id);
this.CurrentTask = asyncTask;
asyncTask.BackgroundWorker.RunWorkerAsync();
}
void AbortTask()
{
if (this.CurrentTask != null && this.CurrentTask.BackgroundWorker.IsBusy)
{
AppendText("Aborting Task " + this.CurrentTask.Id + "...");
this.CurrentTask.IsAborted = true;
this.CurrentTask.BackgroundWorker.CancelAsync();
}
}
void CancelTask()
{
if (this.CurrentTask != null && this.CurrentTask.BackgroundWorker.IsBusy)
{
AppendText("Cancelling Task " + this.CurrentTask.Id + "...");
this.CurrentTask.BackgroundWorker.CancelAsync();
}
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
var backgroundWorker = (BackgroundWorker)sender;
for (var i = 0; i < 10; i++)
{
//check before making call...
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
return;
}
//simulate a call to remote service...
Thread.Sleep(TimeSpan.FromSeconds(10.0));
//check before reporting any progress...
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
return;
}
backgroundWorker.ReportProgress(0);
}
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (this.CurrentTask.IsAborted)
return;
AppendText("[" + DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss") + "] " + "Progress on Task: " + this.CurrentTask.Id + "...");
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (this.CurrentTask.IsAborted)
return;
if (e.Cancelled)
{
AppendText("Cancelled Task: " + this.CurrentTask.Id);
}
else if (e.Error != null)
{
AppendText("Error Task: " + this.CurrentTask.Id);
}
else
{
AppendText("Completed Task: " + this.CurrentTask.Id);
}
//cleanup...
this.CurrentTask.BackgroundWorker.DoWork -= backgroundWorker_DoWork;
this.CurrentTask.BackgroundWorker.ProgressChanged -= backgroundWorker_ProgressChanged;
this.CurrentTask.BackgroundWorker.RunWorkerCompleted -= backgroundWorker_RunWorkerCompleted;
this.CurrentTask= null;
}
Wygląda na to, że to sprowadza się do tego, że chcesz przeprowadzić długi proces, ale pozwól użytkownikowi zabić długo trwający proces i rozpocząć nowy. Czy to jest poprawne ? – TheKingDave
Tak. Ale zadanie można zabić poprzez Anuluj lub Przerwij. Anuluj jest wywoływane przez użytkownika. W tym miejscu wątek jest zamykany, ale można ukończyć aktualizację interfejsu użytkownika. Przerwanie jest wywoływane, gdy drugie zadanie jest tworzone i uruchamiane, gdy inne jest aktualnie wykonywane. Oryginalne zadanie musi zostać przerwane. W tym przypadku nowe zadanie ma pierwszeństwo, a stare przerwane zadanie nie może zaktualizować interfejsu użytkownika i musi "wyjść cicho". –
Więc jeśli "Przerwij" zadanie, czy system powinien zakończyć długi proces, czy nie?Gdybym był użytkownikiem i wybrałem abort, to oczekiwałbym, że system po prostu przestanie wszystko, jeśli wybiorę opcję cancel, oczekuję, że zatrzyma się z wdziękiem. – TheKingDave