任务一般是一些比较耗时的操作(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 task2 = new Task(Func2);
task2.Start();
C#5.0引入了async、await这些关键字,异步方法的写法为:
async Task Func1()
{
await Task.Delay(1000);
}
async Task 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 progress)
{
for (int i = 0; i < 100; i++)
{
await Task.Delay(100);
progress.Report(i + 1);
}
}
调用方法则如下所示:
await Func1(new Progress(p =>
{
Console.WriteLine(p);
}));
对于没有返回值的任务,我们上面已经提到了使用的方法。如果任务有返回值,而我们需要使用这个返回值,调用的方法如下:
(1)使用async、await关键字
async Task Func2()
{
await Task.Delay(1500);
return 1;
}
//调用方法
int result = await Func2();
(2)不使用async、await关键字
int Func2()
{
Thread.Sleep(1500);
return 1;
}
//调用方法
Task task = new Task(Func2);
task.ContinueWith(t =>
{
int result = t.Result;
});
使用WhenAll方法,使用方法如下:
async Task Func1()
{
await Task.Delay(1000);
return 1;
}
async Task Func2()
{
await Task.Delay(1500);
return 2;
}
Task[] tasks = new Task[] { Func1(), Func2() };
int[] results = await Task.WhenAll(tasks);
使用WhenAny方法,使用方法如下:
async Task Func1()
{
await Task.Delay(1000);
return 1;
}
async Task Func2()
{
await Task.Delay(1500);
return 2;
}
Task[] tasks = new Task[] { Func1(), Func2() };
Task 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();