WPF
アプリケーションがあり、コンポーネントを使用しBackgroundWorker
てバックエンドからデータを取得し、UI に表示しています。
BackgroundWorker
には、WorkerReportsProgress = true
UI を定期的に更新できるようにする機能があります。また、ユーザーがキャンセルできるようになっていますBackgroundWorker
。WorkerSupportsCancellation = true
すべてうまくいきます!
3 番目のより複雑な動作を実装しようとすると問題が発生します。基本的に、ユーザーは、BackgroundWorker
現在実行中のタスクを含め、いつでも新しいタスクを開始できる柔軟性を備えている必要があります。タスクが現在実行中で、新しいタスクが開始された場合、古いタスクを としてマークする必要がありますAborted
。タスクは、それ以上の UI 更新を行うことが許可されていないというAborted
点で異なります。「黙ってキャンセル」する必要があります。Cancelled
Aborted
クラスBackgroundWorker
内をラップしてビットを追加しました。内部のビットをチェックして、それ以上の UI 更新を防ぎます。すごい!AsyncTask
IsAborted
IsAborted
ProgressChanged
RunWorkerCompleted
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;
}