2013-06-04 49 views
5

我有一个WPF应用程序,我使用BackgroundWorker组件从后端检索一些数据并将其显示在UI中。跟踪多个背景工作者

BackgroundWorkerWorkerReportsProgress = true,这样就可以定期更新UI。 BackgroundWorker也有WorkerSupportsCancellation = true,以便它可以被用户取消。所有这一切都很好!

我在尝试实现第三个更复杂的行为时遇到了问题。基本上,用户需要有灵活性随时启动新的BackgroundWorker任务,包括当前正在执行的任务。如果任务当前正在执行并且新任务已启动,则需要将旧任务标记为AbortedAborted任务与Cancelled不同,Aborted不允许进行任何进一步的UI更新。它应该被“默默取消”。

我将BackgroundWorker包装在AsyncTask类中,并添加了IsAborted位。检查ProgressChangedRunWorkerCompleted中的IsAborted位,以防止进一步的UI更新。大!

但是,这种方法发生故障,因为当新任务启动时,CurrentTask被替换为AsyncTask的新实例。因此,跟踪CurrentTask变得很困难。

如上所述,在TODO:,这几乎就像我想等到CurrentTask在开始新任务之前中止后完成。但我知道这将提供糟糕的用户体验,因为UI线程将被阻塞,直到旧任务完成。

有没有更好的方法来跟踪多个AsyncTasks确保新的可以按需启动和旧的正确中止没有进一步的UI更新?似乎没有一种跟踪CurrentTask的好方法...... TPL是否提供了一种更好的方式来处理我所追求的内容?

这里是著名的片断我有我的窗口类中:

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

看起来像是这样,你想要运行一个漫长的过程,但允许用户杀死这个长期运行的过程中途,并开始一个新的。它是否正确 ? – TheKingDave

+0

是的。但是,任务可以通过取消或通过中止来终止。取消是由用户调用的。在这里,线程退出,但它可以完成更新UI。当第二个任务被创建时调用Abort,并在另一个任务正在执行时被启动。原始任务必须中止。在这种情况下,新任务优先,旧中止任务无法更新用户界面,并且必须“静静地退出”。 –

+0

因此,如果系统完成长时间运行的过程,如果“中止”任务?如果我是一个用户,我选择了中止,如果我选择取消,我希望系统能够停止所有事情,我希望它能够优雅地停止。 – TheKingDave

回答

9

据我所知,你不想真正放弃线程,你只是希望它继续默默地工作(即不更新用户界面)?一种方法是保留BackgroundWorkers的列表,如果它们被“中止”,则删除它们的事件处理程序。

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); 

这样你可以跟踪所有的工人。由于List维持秩序,您将知道哪一个是最新的(列表中的最后一个),但如果您愿意,您可以在此处轻松使用Stack

+0

感谢您的快速回复。那些正是我寻找的提示。除了包装BackgroundWorker并检查IsAborted之外,仅仅删除事件处理程序更容易管理“中止”线程。另外,Stack是一个完美的数据结构,用于始终跟踪当前的数据,即Top。谢谢! –

+0

不客气:) – keyboardP