6

WPFアプリケーションがあり、コンポーネントを使用しBackgroundWorkerてバックエンドからデータを取得し、UI に表示しています。

BackgroundWorkerには、WorkerReportsProgress = trueUI を定期的に更新できるようにする機能があります。また、ユーザーがキャンセルできるようになっていますBackgroundWorkerWorkerSupportsCancellation = trueすべてうまくいきます!

3 番目のより複雑な動作を実装しようとすると問題が発生します。基本的に、ユーザーは、BackgroundWorker現在実行中のタスクを含め、いつでも新しいタスクを開始できる柔軟性を備えている必要があります。タスクが現在実行中で、新しいタスクが開始された場合、古いタスクを としてマークする必要がありますAborted。タスクは、それ以上の UI 更新を行うことが許可されていないというAborted点で異なります。「黙ってキャンセル」する必要があります。CancelledAborted

クラスBackgroundWorker内をラップしてビットを追加しました。内部のビットをチェックして、それ以上の UI 更新を防ぎます。すごい!AsyncTaskIsAbortedIsAbortedProgressChangedRunWorkerCompleted

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;
}
4

1 に答える 1

11

私が理解していることから、あなたは実際にスレッドを中止したくないのですか? 1 つの方法は、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);

そうすれば、すべてのワーカーを追跡できます。aListは順序を維持するため、最新のもの (リストの最後のもの) を知ることができますが、必要にStack応じてここで a を簡単に使用することもできます。

于 2013-06-04T13:49:53.133 に答える