2013-06-04 11 views
5

Ho un'applicazione WPF e sto utilizzando un componente BackgroundWorker per recuperare alcuni dati dal back-end e visualizzarlo nell'interfaccia utente.Tracking Multiple BackgroundWorkers

Il BackgroundWorker ha WorkerReportsProgress = true in modo che l'interfaccia utente può essere aggiornata periodicamente. Lo BackgroundWorker ha anche WorkerSupportsCancellation = true in modo che possa essere annullato dall'utente. Tutto ciò funziona alla grande!

Ho difficoltà a provare a implementare un terzo e più complesso comportamento. Fondamentalmente, l'utente deve avere la flessibilità per avviare una nuova attività BackgroundWorker in qualsiasi momento, incluso mentre una è attualmente in esecuzione. Se un'attività è attualmente in esecuzione e ne viene avviato uno nuovo, la vecchia attività deve essere contrassegnata come Aborted. Le attività Aborted sono diverse da Cancelled in quanto Aborted non è autorizzato a effettuare ulteriori aggiornamenti dell'interfaccia utente. Dovrebbe essere "cancellato in silenzio".

Ho avvolto il BackgroundWorker all'interno della classe AsyncTask e aggiunto il IsAborted bit. Il controllo dello IsAborted bit all'interno di ProgressChanged e RunWorkerCompleted impedisce ulteriori aggiornamenti dell'interfaccia utente. Grande!

Tuttavia, questo approccio si rompe perché quando nuovi compiti vengono avviate, CurrentTask viene sostituita con una nuova istanza di AsyncTask. Di conseguenza, diventa difficile tenere traccia dello CurrentTask.

Come indicato, nel numero TODO:, è quasi come se volessi aspettare che lo CurrentTask termini dopo un'interruzione prima di iniziare una nuova attività. Ma so che questo fornirà un'esperienza utente negativa in quanto il thread dell'interfaccia utente verrà bloccato fino al completamento della vecchia attività.

C'è un modo migliore per tenere traccia di più AsyncTasks assicurandosi che quelli nuovi possano essere attivati ​​su richiesta e quelli vecchi vengono interrotti correttamente senza ulteriori aggiornamenti dell'interfaccia utente? Non sembra essere un buon modo per tenere traccia del CurrentTask ... Quella TPL offre un modo migliore per gestire quello che sto cercando?

Qui ci sono i frammenti importanti che ho dentro la mia classe Window:

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

Sembra che si tratti di eseguire un processo lungo, ma consentire all'utente di terminare a metà processo questo processo a esecuzione prolungata e avviarne uno nuovo. È corretto ? – TheKingDave

+0

Sì. Tuttavia, un'attività può essere eliminata tramite Annulla o Abort. Annulla viene chiamato dall'utente. Qui, il thread termina ma è libero di completare l'aggiornamento dell'interfaccia utente. L'interruzione viene chiamata quando viene creata una seconda attività e avviata se un'altra era in esecuzione. L'attività originale deve essere interrotta. In questo caso, la nuova attività ha la precedenza e la vecchia attività interrotta non può aggiornare l'interfaccia utente e deve "uscire silenziosamente". –

+0

Quindi, se si "interrompe" l'attività, il sistema dovrebbe terminare il processo a lungo termine o no?Se fossi un utente e selezionassi l'abort mi sarei aspettato che il sistema interrompesse tutto se avessi selezionato cancel mi sarei aspettato che si fermasse con grazia. – TheKingDave

risposta

9

Da quello che ho capito, non si vuole interrompere in realtà il filo, si vuole solo che continui a lavorare in silenzio (cioè non aggiornare l'interfaccia utente)? Un modo sarebbe quello di mantenere un elenco di BackgroundWorkers e rimuovere i loro gestori di eventi se devono essere "abortiti".

List<BackgroundWorker> allBGWorkers = new List<BackgroundWorker>(); 

//user creates a new bg worker. 
BackgroundWorker newBGWorker = new BackgroundWorker(); 
//.... fill out properties 


//before adding the new bg worker to the list, iterate through the list 
//and ensure that the event handlers are removed from the existing ones  
foreach(var bg in allBGWorkers) 
{  
    bg.ProgressChanged -= backgroundWorker_ProgressChanged; 
    bg.RunWorkerCompleted -= backgroundWorker_RunWorkerCompleted; 
} 

//add the latest bg worker you created 
allBGWorkers.Add(newBGWorker); 

In questo modo è possibile tenere traccia di tutti i lavoratori. Poiché un ordine List mantiene l'ordine, saprai quale è l'ultimo (l'ultimo nell'elenco), ma puoi anche usare facilmente uno Stack qui se preferisci.

+0

Grazie per la rapida risposta. Questi sono esattamente i suggerimenti che stavo cercando. Invece di eseguire il wrapping di BackgroundWorker e il controllo su IsAborted, la semplice rimozione dei gestori di eventi è molto più semplice per gestire un thread "interrotto". Inoltre, una pila è una struttura dati perfetta per tracciare sempre la corrente, cioè il Top. Grazie! –

+0

Prego :) – keyboardP