c# - 跟踪多个 BackgroundWorker

标签 c# .net wpf task-parallel-library backgroundworker

我有一个 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 是否提供更好的方法来处理我所追求的?

以下是我的 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;
}

最佳答案

据我了解,您并不想真正中止线程,您只是希望它继续静默工作(即不更新 UI)?一种方法是保留一个 BackgroundWorker 列表,并在它们要“中止”时删除它们的事件处理程序。

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

这样你就可以跟踪所有的 worker 。由于 List 保持顺序,您将知道哪个是最新的(列表中的最后一个),但您可以在这里轻松地使用 Stack 如果您更喜欢。

关于c# - 跟踪多个 BackgroundWorker,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16919426/

相关文章:

c# - WPF 列表框绑定(bind)

javascript - jquery Accordion 内的 asp 按钮不引发服务器事件

.net - 使用 Style 在 WPF 中禁用列重新排序

c# - IOCP 是在 I/O 发生时或之后运行的线程吗?

c# - 通用查找方法

c# - 如何在 WPF 中为我的弹出窗口应用图标?

wpf - 在 WPF 中本地化 ContextMenu 项标题

c# - 创建自定义 AppDomain 并向其添加程序集

c# - 将语音文化改变为其他语言

c# - 处理 Epplus Excel 转换为 HTML 中的合并单元格