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