65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (12投票s)

2013年12月2日

CPOL

5分钟阅读

viewsIcon

56067

downloadIcon

1965

保持 WPF 应用程序的响应性涉及到使用后台进程来处理长时间运行的任务,而 TPL 使这项编程任务比以往任何时候都更容易。它还提供了一种异步取消任务以及从后台进程更新 UI(例如,使用进度条)的方法。

Sample Image

引言

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()

取消

启用取消的规则是:

  1. 在将引发取消请求的线程中创建 CancellationTokenSource,在我的示例中这是 UI 线程。
  2. 创建 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 屏幕。计算结果通过 fibTaskResult 属性返回到 UI 线程。我们可以使用取消令牌的 IsCancellationRequested 属性来检查操作是否已被取消。

更新进度条

更新进度条的规则很简单:

  1. 将 UI 线程传递给长时间运行的方法以进行同步。
  2. 将进度条更新包装在一个与 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 月 - 初始发布。
© . All rights reserved.