2013-06-04 8 views
5

私はWPFアプリケーションを持っており、BackgroundWorkerコンポーネントを使ってバックエンドからデータを取り出し、UIに表示しています。複数のBackgroundWorkersをトラッキングする

BackgroundWorkerWorkerReportsProgress = trueであり、UIを定期的に更新することができます。 BackgroundWorkerにはWorkerSupportsCancellation = trueも含まれているため、ユーザーはキャンセルすることができます。すべてがうまくいく!

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

AsyncTaskクラス内にBackgroundWorkerをラップし、IsAbortedビットを追加しました。 ビットをProgressChangedRunWorkerCompletedにチェックすると、それ以上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; 
} 
+0

長いプロセスを実行したいが、ユーザーがこの長い実行中のプロセスを途中で終了して新しいプロセスを開始できるようにしたい場合は、これは正しいです ? – TheKingDave

+0

はい。しかし、タスクは、CancelまたはAbortを介して終了することができます。キャンセルはユーザーによって呼び出されます。ここでは、スレッドは終了しますが、UIの更新は自由に終了します。 2番目のタスクが作成され、別のタスクが現在実行されている場合に開始されると、中止が呼び出されます。元のタスクを中止する必要があります。この場合、新しいタスクが優先され、古い中断されたタスクはUIを更新することができず、「サイレントモードで終了する」必要があります。 –

+0

したがって、システムが長時間実行しているプロセスを終了するかどうか、タスクを「中止する」としたら?私がユーザーで、中止を選択した場合は、キャンセルを選択するとシステムがすべて停止することが期待されます。正常に停止すると予想します。 – TheKingDave

答えて

9

、あなたが実際にスレッドを中断したくない、あなたはそれを黙って作業を続けたい(すなわちありませんUIを更新しますか?) 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); 

こうして、すべての労働者を追跡することができます。 Listは注文を維持しているので、最新のもの(リストの最後のもの)がわかりますが、好きなだけStackを簡単に使うこともできます。

+0

迅速な返信ありがとうございます。それらはまさに私が探していたヒントです。 BackgroundWorkerをラップしてIsAbortedをチェックするのではなく、イベントハンドラを単に削除するだけで、「中止された」スレッドを管理する方がはるかに簡単です。さらに、スタックは、常に現在の、すなわちトップを常に追跡するための完全なデータ構造である。ありがとう! –

+0

あなたは大歓迎です:) – keyboardP

関連する問題