任务一般是一些比较耗时的操作(IO或者复杂计算),如果在主线程运行,将影响程序的流畅性。所以,我们一般会新建线程处理任务。.NET4开始引进了Task,它对Thread做了大量方便易用的封装。我们将详细讲述Task的使用方法,以及各种多线程的使用场合。
一个简单的耗时操作如下所示:
void Func1() { Thread.Sleep(1000); }如果有返回值,将如下所示:
int Func2() { Thread.Sleep(1500); return 1; }耗时操作如果要新建线程去运行,可以这么写:
Task task1 = new Task(Func1); task1.Start(); Task<int> task2 = new Task<int>(Func2); task2.Start();C#5.0引入了async、await这些关键字,异步方法的写法为:
async Task Func1() { await Task.Delay(1000); } async Task<int> Func2() { await Task.Delay(1500); return 1; }调用时就像同步的方法一样:
Func1(); Func2();需要注意的是,虽然代码看上去是先执行Func1,然后再执行Func2,但实际上,这两个都是异步方法,它们会同时开始执行。
在使用Thread时会看到一个Abort方法,用来终止线程。但其实这个方法非常不安全,甚至不能终止,一般不提倡使用。事实上,如果一个耗时方法是我们自己设计的,我们可以想办法在中途退出;而如果这个方法我们只能调用,无法修改,那这个任务基本无法中止。
假设有一个耗时方法如下所示:
async Task Func1() { await Task.Delay(10000); }一旦调用,这个方法将运行10秒。为了避免这种情况,我们需要对等待进行切割。如:
async Task Func1() { for (int i = 0; i < 100; i++) { await Task.Delay(100); } }如果希望中途能够取消,那么可以写成:
async Task Func1(CancellationToken cancellationToken) { for (int i = 0; i < 100; i++) { if (cancellationToken.IsCancellationRequested) { return; } await Task.Delay(100); } }调用的方法如下:
CancellationTokenSource cts = new CancellationTokenSource(); await Func1(cts.Token);如果需要在另一个线程里面取消,只需要调用以下语句即可:
cts.Cancel();
有时候我们需要实时知道耗时任务的运行进度,例如下载,可能需要向用户提供一个完成百分比。
带进度报告功能的异步任务设计如下:
async Task Func1(IProgress<int> progress) { for (int i = 0; i < 100; i++) { await Task.Delay(100); progress.Report(i + 1); } }调用方法则如下所示:
await Func1(new Progress<int>(p => { Console.WriteLine(p); }));
对于没有返回值的任务,我们上面已经提到了使用的方法。如果任务有返回值,而我们需要使用这个返回值,调用的方法如下:
(1)使用async、await关键字
async Task<int> Func2() { await Task.Delay(1500); return 1; } //调用方法 int result = await Func2();(2)不使用async、await关键字
int Func2() { Thread.Sleep(1500); return 1; } //调用方法 Task<int> task = new Task<int>(Func2); task.ContinueWith(t => { int result = t.Result; });
使用WhenAll方法,使用方法如下:
async Task<int> Func1() { await Task.Delay(1000); return 1; } async Task<int> Func2() { await Task.Delay(1500); return 2; } Task<int>[] tasks = new Task<int>[] { Func1(), Func2() }; int[] results = await Task.WhenAll(tasks);使用WhenAny方法,使用方法如下:
async Task<int> Func1() { await Task.Delay(1000); return 1; } async Task<int> Func2() { await Task.Delay(1500); return 2; } Task<int>[] tasks = new Task<int>[] { Func1(), Func2() }; Task<int> task = await Task.WhenAny(tasks); int result = task.Result;WhenAny方法可以有以下的应用场景:
(1)超时判断。写一个简单的延时任务,跟需要执行的任务一同放在列表里。如果WhenAny先返回延时任务,说明需要执行的任务已经超时了。
(2)一个任务完成,取消执行余下任务。结合CancellationToken完成这一功能,具体代码如下:
async Task Func1(CancellationToken cancellationToken) { for (int i = 0; i < 20; i++) { if (cancellationToken.IsCancellationRequested) { return; } await Task.Delay(100); } } async Task Func2(CancellationToken cancellationToken) { for (int i = 0; i < 100; i++) { if (cancellationToken.IsCancellationRequested) { return; } await Task.Delay(110); } } CancellationTokenSource cts = new CancellationTokenSource(); Task[] tasks = new Task[] { Func1(cts.Token), Func2(cts.Token) }; Task task = await Task.WhenAny(tasks); cts.Cancel();