在现代应用程序开发中,后台任务的管理和调度是提高应用响应性和用户体验的关键。对于使用Windows Presentation Foundation (WPF) 的开发者来说,BackgroundWorker类提供了一种机制来执行后台任务,同时保持UI的响应性。然而,随着Task Parallel Library (TPL) 的引入,后台任务的创建和管理变得更加简单和高效。
在TPL出现之前,创建和管理后台任务,尤其是在WinForm和WPF应用程序中,需要程序员手动处理这些任务的代码。BackgroundWorker类提供了一种预定义的机制来同步后台和UI任务,但它涉及到理解BackgroundWorker是如何提供这些能力的(使用异步回调模型)。BackgroundWorker是对以临时方式处理这些编程任务的改进,但现在有了更新、更好的模型。所有这些功能现在都由新的TPL和CancellationToken机制提供。
本文附带的简单应用程序演示了如何使用TPL在WPF应用程序中添加后台处理和进度条。应用程序使用一个低效的递归算法来计算斐波那契数列的第n项,这是一个长时间运行过程的完美示例。滑块条上的值大于40将需要足够的时间来允许在操作完成之前取消操作。滑块条用于设置要计算的数列中的元素编号。提供了两个按钮,"开始异步"和"取消异步",这两个按钮的功能是自解释的。计算结果显示在文本框中。
"开始异步"按钮的处理程序包含启动后台进程的算法的代码。它还创建了一个CancellationToken,该Token传递给后台进程,以便它可以测试取消请求。实际算法包含在ComputeFibonacci方法中。正如源代码中所看到的,添加异步功能到代码中不需要太多改动。
private void StartAsyncButton_Click(object sender, RoutedEventArgs e)
{
tokenSource = new CancellationTokenSource();
var ct = tokenSource.Token;
Task fibTask = Task.Factory.StartNew(() => ComputeFibonacci(numberToCompute, ct, UISyncContext), ct);
fibTask.ContinueWith((antecedent) =>
{
if (ct.IsCancellationRequested)
{
ManageDisplay("已取消", false);
}
else
{
ManageDisplay(fibTask.Result.ToString(), false);
}
}, UISyncContext);
}
要启用取消操作,需要遵循以下规则:
private void CancelAsyncButton_Click(object sender, RoutedEventArgs e)
{
tokenSource.Cancel();
}
接下来,需要启动后台进程,但首先需要保存UI上下文以用于同步目的:
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
Task fibTask = Task.Factory.StartNew(() => ComputeFibonacci(numberToCompute, ct, UISyncContext), ct);
最后,需要链接一个任务来显示ComputeFibonacci方法的结果,这是TPL的一个特性:
fibTask.ContinueWith((antecedent) =>
{
if (ct.IsCancellationRequested)
{
ManageDisplay("已取消", false);
}
else
{
ManageDisplay(fibTask.Result.ToString(), false);
}
}, UISyncContext);
更新进度条的规则很简单:
private long ComputeFibonacci(int n, CancellationToken token, TaskScheduler uiTask)
{
token.ThrowIfCancellationRequested();
// 执行斐波那契计算...
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
Task.Factory.StartNew(() =>
{
progressBar1.Value = highestPercentageReached;
}, token, TaskCreationOptions.AttachedToParent, uiTask);
}
}