2013-06-04 17 views
5

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

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

+0

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". –

+0

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

Odpowiedz

9

Z tego co rozumiem, że nie chcesz, aby rzeczywiście przerwać nić, po prostu chcesz to kontynuować pracę w trybie cichym (czyli nie zaktualizować interfejs użytkownika)? Jednym ze sposobów jest zachowanie listy BackgroundWorkers i usunięcie ich programów obsługi zdarzeń, jeśli mają zostać "przerwane".

W ten sposób można śledzić wszystkich pracowników. Ponieważ numer List zachowuje porządek, będziesz wiedzieć, który z nich jest najnowszy (ostatni na liście), ale możesz też z łatwością korzystać z adresu Stack tutaj, jeśli wolisz.

+0

Dziękuję za szybką odpowiedź. To są właśnie wskazówki, których szukałem. Zamiast owijać BackgroundWorker i sprawdzać przy IsAborted, po prostu usunięcie programów obsługi zdarzeń jest znacznie łatwiejsze w zarządzaniu "przerwanym" wątkiem. Ponadto stos to idealna struktura danych do ciągłego śledzenia prądu, tj. Do góry. Dzięki! –

+0

Nie ma za co :) – keyboardP