在 WPF 中使用任务并行库 (TPL)






4.75/5 (12投票s)
保持 WPF 应用程序的响应性涉及到使用后台进程来处理长时间运行的任务,而 TPL 使这项编程任务比以往任何时候都更容易。它还提供了一种异步取消任务以及从后台进程更新 UI(例如,使用进度条)的方法。
引言
TPL 可用于在长时间运行的任务期间保持桌面应用程序的响应性。那些使用 BackgroundWorker
来保持应用程序响应性、提供异步取消以及从后台进程更新进度条的程序员将能够看到 Microsoft 最新的任务调度和管理实现如何提供一个更简单、更易于使用的机制,用于在 WPF 应用程序中使用后台任务。
背景
在 TPL 出现之前,创建和管理后台任务,尤其是在 WinForm 和 WPF 应用程序中,需要程序员处理这些任务的代码。BackgroundWorker
类能够提供一个预定义的机制,用于提供后台和 UI 任务同步,但这涉及到理解 BackgroundWorker
如何提供这些功能(使用异步回调模型)。BackgroundWorker
比以临时方式处理这些编程任务是一个进步,但现在有了更好、更新的模型。所有这些功能现在都由新的 TPL 和 CancellationToken 机制提供。
使用代码
本文配套的简单应用程序中的代码演示了如何使用 TPL 为 WPF 应用程序添加后台处理和进度条。该应用程序使用了一个低效的递归算法来计算斐波那契数列的第 n 个值,这是一个典型的长时间运行的进程。滑块条上大于 40 的值将需要足够长的时间,以便您可以在操作完成之前取消它。滑块条用于设置要计算的数列中的元素编号。提供了两个按钮:“Start Async”(异步开始)和“Cancel Async”(异步取消),它们的意思不言自明。计算结果显示在文本框中。
StartAsync
按钮处理程序包含启动后台进程中算法的代码。它还创建了 CancellationToken
,并将其作为参数传递给后台进程,以便它可以测试取消请求。实际算法包含在 ComputeFibonacci
方法中。如您在源代码中所见,要为代码添加异步功能,只需进行少量更改。要取消操作,CancelAsync
处理程序会像这样调用 CancellationTokenSource
上的 Cancel
方法:tokenSource.Cancel()
。
取消
启用取消的规则是:
- 在将引发取消请求的线程中创建
CancellationTokenSource
,在我的示例中这是 UI 线程。 - 创建
CancellationToken
并将其作为参数传递给长时间运行的方法 (ComputeFibonacci
) 和运行该方法的 Task (fibTask
)。
让我们来看一些代码细节。每次启动进程时,都需要创建一个新的 CancellationToken
实例。
tokenSource = new CancellationTokenSource();
var ct = tokenSource.Token;
...
我们可以将取消令牌 (ct) 传递给后台进程,以便它们可以“协作”处理取消请求。
创建后台任务
接下来,我们需要启动后台进程,但首先我们需要保存 UI 上下文以进行同步。
//this gets the current UI thread context
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
//this creates a new task and starts the process. Notice we also pass the
//cancellation token, ct, to both the ComputeFibonacci method and to the Task
Task<long> fibTask = Task.Factory.StartNew(() => ComputeFibonacci(numberToCompute, ct, UISyncContext), ct);
...
最后,我们需要链接一个任务来显示 ComputeFibonacci
方法的结果,这是 TPL 的一项功能。
fibTask.ContinueWith((antecedent) =>
{
if (ct.IsCancellationRequested)
{
//cleanup here after cancel
ManageDisplay("Canceled", false);
}
else
{
ManageDisplay(fibTask.Result.ToString(), false);
}
}, UISyncContext);
...
ContinueWith
方法将新任务链接到“antecedant”(前置)任务,使其在前一个任务完成后自动运行。此外,第二个任务使用我们之前检索到的 UISyncContext
自动与 UI 线程同步,该上下文作为参数传递给 ContinueWith
方法。这一点很重要,因为您不能在 UI 线程以外的任何线程中更新 UI 屏幕。计算结果通过 fibTask
的 Result
属性返回到 UI 线程。我们可以使用取消令牌的 IsCancellationRequested
属性来检查操作是否已被取消。
更新进度条
更新进度条的规则很简单:
- 将 UI 线程传递给长时间运行的方法以进行同步。
- 将进度条更新包装在一个与 UI 线程同步的任务中。
让我们看看那是什么样子的。
private long ComputeFibonacci(int n, CancellationToken token, TaskScheduler uiTask)
{
//check to see if cancel requested, I can do it like this in a Task
token.ThrowIfCancellationRequested();
//perform the fibonacci calculation here
...
//check for update to the progress bar
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
//this creates a child task for displaying the progress bar updates
Task.Factory.StartNew(() =>
{
progressBar1.Value = highestPercentageReached;
}, token, TaskCreationOptions.AttachedToParent, uiTask);
}
}
ComputeFibonacci
方法中的第一行代码检查是否已发送用户取消请求。这就是处理取消请求所需的一切,如果按下了“Cancel Async”按钮,代码将不会继续执行到此行。接下来,此版本的 StartNew
方法同时接受 CancellationToken
(token) 和同步线程 (uiTask
) 作为参数。我不使用取消令牌,但如果我需要,它也可以在此子任务中可用。TaskCreateOptions.AttachedToParent
参数表示我希望此任务作为子任务运行在父线程 (ComputeFibonacci
) 下。基本上,当父线程被取消时,这会自动取消子线程。
就是这样!我现在有了一个应用程序,它在一个后台启动一个长时间运行的算法,向 UI 显示进度,并允许用户异步取消长时间运行的进程。这些简单的步骤可以根据需要重复,以达到所需的复杂程度,从而完成您的编程需求。
关注点
有些人可能在想:“他为什么每次更新进度条都要创建和销毁一个线程?”嗯,TPL 的妙处在于这些都是“轻量级”线程,可以以很少的开销实例化。它们的创建几乎没有性能损失,所以您不需要费很多周折来使用它。只需要在需要时创建它,在完成后丢弃它。
历史
- 2013 年 12 月 - 初始发布。