说起多线程,想到更多的是线程Thread,那么我们今天说的任务Task和线程有什么关系呢?
- 任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行;
- 线程和任务并不是一对一的关系,比如开10个任务并不是说就会开10个线程,这一点任务和线程池有点类似,但是任务相比线程池开销更小控制也更为精确;
关于Task
Task标识一个异步操作,创建Task有两种方法,一种类似于创建对象直接new出来,一种是通过工厂创建。
public void CreateTask()
{
//直接实例化
var task1 = new Task(() =>
{
Console.WriteLine("任务1创建成功!");
});
task1.Start();
//通过静态工厂创建
var task2 = Task.Factory.StartNew(() => {
Console.WriteLine("任务2创建成功!");
});
}
下面来看一下一个Task的生命周期,编写测试代码:
public void GetTaskStatus()
{
//直接实例化
var task1 = new Task(() =>
{
Console.WriteLine("开始");
Thread.Sleep(10000);
Console.WriteLine("结束");
});
Console.WriteLine(task1.Status);
task1.Start();
Console.WriteLine(task1.Status);
task1.Wait();
Console.WriteLine(task1.Status);
}
运行结果:

由此可见 一个Task的生命周期中至少包含以下三个阶段:
- Created: Task被创建
- WaitingToRun :等待执行
- RanToCompletion: 任务执行完成
其实至少应该还包含一个阶段:Running表示运行中。
下面谈谈Task的任务控制,这也是Task的优势所在,可以使以前无法控制的并行过程按照自己的需求变得有序可控起来。
Task.Wait
上面的示例中已经用过了,就是等待任务执行完成,阻塞主线程,多用于线程(任务)与主线程同步。
Task.WaitAll
顾名思义,多个任务并行执行时,等待所有任务执行完成,实例代码:
public void WaitAllTask()
{
var task1 = new Task(() =>
{
Console.WriteLine("任务一开始!");
Thread.Sleep(2000);
Console.WriteLine("任务一结束!");
});
var task2 = new Task(() =>
{
Console.WriteLine("任务二开始!");
Thread.Sleep(2000);
Console.WriteLine("任务二结束!");
});
task1.Start();
task2.Start();
Task.WaitAll(task1,task2);
Console.WriteLine("所有任务执行完成!");
}
执行结果:

可见等到两个任务全部结束后,主线程才提示任务结束。
Task.WaitAny
等待任意一个任务执行结束,上面的示例代码修改下:
task1.Start();
task2.Start();
Task.WaitAny(task1, task2);
Console.WriteLine("有任务执行完成!");
执行结果:

可见任务一结束后主线程就提示有任务完成了。
Task.ContinueWith
一个Task任务后自动开启另一个Task,实现Task的延续。实例代码如下:
public void TaskContinue()
{
var task1 = new Task(() =>
{
Console.WriteLine("任务一开始!");
Thread.Sleep(2000);
Console.WriteLine("任务一结束!");
});
var task2 = new Task(() =>
{
Console.WriteLine("任务二开始!");
Thread.Sleep(20000);
Console.WriteLine("任务二结束!");
});
task1.Start();
task2.Start();
var result = task1.ContinueWith(task =>
{
Console.WriteLine("任务一完成");
return String.Format("任务一结束后状态{0}",task.Status);
});
Console.WriteLine(result.Result.ToString());
}
运行结果:

Task的取消
前面说了那么多Task的用法,下面我们来看Task的取消,比如我们启动了一个Task,出现了遗产或者用户主动选择取消,我们是可以取消这个任务的。在任务Task中,我们通过calcellation的tokens来取消一个Task,有些时候在Task的Body里面会包含循环,我们可以在轮询的时候判断IscancellationRequested的属性是否为True,如果为True的时候就return或者抛出异常。下面在代码中我们看下如何实现任务的取消:
public void TaskCancel()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Factory.StartNew(() => {
Int32 sec = 0;
while (true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("任务被终止,即将退出");
break;
}
else
{
Console.WriteLine("计时{0}s", sec);
}
Thread.Sleep(1000);
sec += 1;
}
},token);
token.Register(() => {
Console.WriteLine("任务取消");
});
Console.WriteLine("按任意键退出计时");
Console.ReadKey();
tokenSource.Cancel();
}
这里开启了一个Task,并给token注册了一个方法,输出信息,然后等待用户输入,用户输入任意信息后,执行tokenSource.Cancel方法,任务随即被取消,执行结果如下:

Task的嵌套
Task中还可以再嵌套Task,Task中的嵌套可以分为两种。关联嵌套和非关联嵌套,也就是说内层的Task和外层的Task是否有关联,下面我们代码示例来说明:
public void TaskWithTask()
{
var pTask = Task.Factory.StartNew(() => {
var cTask = Task.Factory.StartNew(() => {
Thread.Sleep(1000);
Console.WriteLine("子任务完成");
});
Console.WriteLine("父任务完成");
});
pTask.Wait();
Console.WriteLine("非关联嵌套");
}
运行结果:

由执行结果可知,外层的pTask运行完成后,并不会等待内层的cTask,直接向下走了,其实这种嵌套相当于我们创建了两个Task,只是写到一起,代码可读性更强,并且可以在一个Task中加入多个Task,让进度并行前进。
再看一个实例:
public void TaskWithTask()
{
var pTask = Task.Factory.StartNew(() =>
{
var cTask = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("子任务完成");
},TaskCreationOptions.AttachedToParent);
Console.WriteLine("父任务完成");
});
pTask.Wait();
Console.WriteLine("非关联嵌套");
}
我们在创建cTask的时候,加入了参数TaskCreationOptions.AttachedToParent,这个时候pTask和cTask就产生了关联,cTask就会成为pTask的一部分,执行结果:

可见tTask会等待cTask执行完成。
下面我们来看一个task综合使用的例子,看一些多任务是如何协作的,假设这样一个场景:任务2和任务3要等待任务1完成后,取得任务1的结果,然后开始执行。任务4要等待任务2完成,取得其结果才能执行,最终任务3和任务4都完成了,合并结果,任务完成。
代码如下:
public void MutiTasks()
{
var t1 = Task.Factory.StartNew<Int32>(() => {
Console.WriteLine("任务一执行");
return 1;
});
t1.Wait();
var t3 = Task.Factory.StartNew<Int32>(() => {
Console.WriteLine("任务三执行");
return t1.Result+3;
});
var t4 = Task.Factory.StartNew<Int32>(() =>
{
Console.WriteLine("任务二执行");
return t1.Result + 2;
}).ContinueWith<Int32>(task => {
Console.WriteLine("任务四执行");
return task.Result + 4;
});
Task.WaitAll(t3,t4);
Console.WriteLine(t3.Result + t4.Result);
}
运行结果:

Task的异常处理
任何应用程序都需要异常处理机制,谁也无法保证自己写得代码在任何时候都是可以正常运行的,那么在Task中到底该怎么处理异常呢?我们先尝试一下用try-catch
public void TaskExcetion()
{
try
{
var pTask = Task.Factory.StartNew(() =>
{
var cTask = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
throw new Exception("子任务异常");
Console.WriteLine("子任务完成");
});
throw new Exception("父任务异常");
Console.WriteLine("父任务完成");
});
pTask.Wait();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("非关联嵌套");
}
看一下结果:

可见异常信息并不对,其实正确的姿势是这个样子的:
public void TaskAggregateExcetion()
{
try
{
var pTask = Task.Factory.StartNew(() =>
{
var cTask = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
throw new Exception("子任务异常");
Console.WriteLine("子任务完成");
});
throw new Exception("父任务异常");
Console.WriteLine("父任务完成");
});
pTask.Wait();
}
catch (AggregateException ex)
{
foreach (Exception item in ex.InnerExceptions)
{
Console.WriteLine(item.Message);
}
}
Console.WriteLine("非关联嵌套");
}
运行结果:

这里用了AggregateException,就是异常集合,当然开发中不会只有一个线程,肯定会有多个线程,多个线程就可能有多个异常。当然,除了在task中使用异常,我们还可以通过Task的几个属性来判断Task的状态,如:IsCompleted, IsFaulted, IsCancelled,Exception等等来判断task是否成功的执行了。