毫无疑问,当一个任务需要较长时间才能完成进,如果有一个进度条显示进度,会比简单的显示一个Please Wait要让人感觉好很多。
然而,一旦涉及进度条,那么,程序至少需要同时做两件事(好吧,也许叫异步的做两件事更确切一点):第一,完成任务本身;第二,计算进度并更新UI。在C# 5.0 Aysnc中提供的IProgress<T>类,为进度条编程提供了便捷的方法。三步即可完成一个简单的进度条。
下面的示例基于Async CTP制作。要能编译、运行示例,需要以下准备:
- Visual Studio 2010 SP1.
- Async CTP(SP1 Refresh)
- 一个WPF应用程序,并且有一个进度条,顺便给进度条取个名,例如progressBar之类的 :-)
如果你还不知道Async是什么,点这里。
好了,这样,我们就可以开始示例了。
第一步,写一个类,用于存放参数,我们用这些参数来计算进度。一个最简单的例子就是有两个整数值,一个是当前完成的值,另一个是总值。所以,我们就有了这样一个类:
1: //Step 1: Have a class to store current value / total value.
2: public class ProgressPartialResult
3: {
4: public int Current { get; set; }
5: public int Total { get; set; }
6: }
有些时候,计算进度可能相当的复杂,但是,原则是一样的,把需要的参数全部做在这个类里就行了。当然,也有一种简单的极品:这个类就是一个System.Integer :-)那你就不用自己写了。
第二步,创建一个IProgress<T>的对像,其中,T就是我们刚才创建的类,并且添加相应的处理逻辑,用来处理当进度更新发生时要做的事情。示例中,一旦进度更新,我们就更新UI里的进度条。计算也很简单:当前值/总值*100。
1: private void DoProgress()
2: {
3: //Step 2: Create an object of IProgress<T> to trace the progress & add event handler when new progress reported.
4: var progress = new Progress<ProgressPartialResult>();
5: progress.ProgressChanged += new ProgressEventHandler<ProgressPartialResult>(progress_ProgressChanged);
6:
7: //Step 3: Call async logic with progress report.
8: DoSomething(progress);
9: }
10:
11: void progress_ProgressChanged(object sender, MainWindow.ProgressPartialResult value)
12: {
13: progressBar.Value = (float)value.Current / value.Total * 100;
14: }
好了,我想你已经知道第三步要做什么了:异步完成任务并且汇报进度。
1: private async void DoSomething(IProgress<ProgressPartialResult> progress)
2: {
3: int total = 100;
4: for (int i = 0; i <= total; i++)
5: {
6: await TaskEx.Delay(20); //Do things for 0.02 seconds.
7: //Report the progress
8: if (progress != null)
9: {
10: progress.Report(new ProgressPartialResult() { Current = i + 1, Total = total });
11: }
12: }
13: progress.Report(new ProgressPartialResult() { Current = 0, Total = total });
14: }
稍微看一下代码,我们假设总量是100,进度for循环以后,首先做任务本身,我们假设每次做0.02秒。然后,就汇报一下进度。当退出循环以后,把进度置为0。
整理一下,整个事情其实是这样的,进度条编程围绕IProgress<T>的对象展开。每当我们在异步的做事的时候,我们就调用Report方法汇报一下进度,进度的参数我们通过一个预先定义好的类来传递。Report()方法会触发ProgressChanged事件,于是,就会调用事件处理者(Handler)逻辑。处理者则根据传递进来的参数,计算出当前的进度,并且做程序员预期它做的事,例如,更新UI上的进度条之类的。
大家可以试一下进度条行进时拖动UI做点别的事玩玩^v^希望本文能够给你带来一些娱乐 :-)
贴个完整的Code Behind。UI部分大家就自行搞定吧。最最后面附简陋的应用程序贴图。
using System;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DoProgress();
}
//Step 1: Have a class to store current value / total value.
public class ProgressPartialResult
{
public int Current { get; set; }
public int Total { get; set; }
}
private void DoProgress()
{
//Step 2: Create an object of IProgress<T> to trace the progress & add event handler when new progress reported.
var progress = new Progress<ProgressPartialResult>();
progress.ProgressChanged += new ProgressEventHandler<ProgressPartialResult>(progress_ProgressChanged);
//Step 3: Call async logic with progress report.
DoSomething(progress);
}
void progress_ProgressChanged(object sender, MainWindow.ProgressPartialResult value)
{
progressBar.Value = (float)value.Current / value.Total * 100;
}
private async void DoSomething(IProgress<ProgressPartialResult> progress)
{
int total = 100;
for (int i = 0; i <= total; i++)
{
await TaskEx.Delay(20); //Do things for 2 seconds.
//Report the progress
if (progress != null)
{
progress.Report(new ProgressPartialResult() { Current = i + 1, Total = total });
}
}
progress.Report(new ProgressPartialResult() { Current = 0, Total = total });
}
}
}
7/5:关于原文的一点点补充:
前文提到,进度条编程围绕IProress<T>展开。那么,为什么我们需要这个接口,换句话说,IProgress<T>给我们带来了什么?个人的理解如下,如果我们要异步的完成一个任务,例如DoSomething,并且我们已经有一系列函数可以帮且我们完成这个任务了,那么,调用这个函数时,我们可能会接触到以下一些函数的签名:
void DoSomething();
Task DoSomethingAsync();
Task DoSomethingAsync(CancellationToken ct);
Task DoSomethingAsync(CancellationToken ct, IProgress<int> progress);
再如果一下,如果我们想要知道DoSomething的进度,选哪一个会更合理一些?……好吧,每个人都有选择的权力^v^大家自己参?OK。
其实这四个函数就像四个员工,第一个员工闷头干活,什么都不管;第二个会找别人干活,他本身负责协调,适合同一时间多做几件事(好同志啊),但是,一件活他一旦开始做了,天塌下来他也会继续做;第三个员工,跟二个差不多,只是随叫随停;第四个不仅随叫随停,而且,经常汇报进度。
所以,回到IProgress<T>上来,因为有了它,我们调用有它的函数时,虽然不知道执行的细节,仍然可以轻易的知道事态的进度;在写函数时,如果想要提供一个“进度友好”的函数,不管是给自己还是给别人用,把IProgress<T>作为参数都是一个不错的选择。
Little knowledge is dangerous.