延申阅读:
1) 多线程之Thread
2) 多线程之线程池
3) 多线程之Task
一、什么是Task
Task表示一个基于线程池线程的单元操作,通常以异步方式在线程池线程上执行。
二、Task的使用
以实例的方式创建Task
var task1 = new Task(() =>
{
Console.WriteLine("this is a task(1)");
});
task1.Start(); //创建实例
类方法创建Task
var task2 = Task.Factory.StartNew(() =>
{
Console.WriteLine("this is a task(2)");
});
var task3 = Task.Run(() => //Task.Run是Task.Factory.StartNew的一种快捷方式
{
Console.WriteLine("this is a task(3)");
});
Task.WaitAll(task1, task2, task3); //阻塞主线程,直到所有task完成
Console.WriteLine("已完成!");
三、Task属性和状态
1、属性
IsCompleted 指明此 Task 是否已完成
IsFaulted 指明Task 是否由于未经处理异常的原因而完成
Status 获取此任务的 TaskStatus
2、状态
1) 初始状态
TaskStatus.Created 该任务已初始化,但尚未被计划。使用Task构造函数创建Task实例时的初始状态。
TaskStatus.WaitingForActivation 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。一个任务的初始状态,这个任务只有当其依赖的任务完成之后才会被调度。
TaskStatus.WaitingToRun 该任务已被计划执行,但尚未开始执行。使用TaskFactory.StartNew创建的任务的初始状态。
2)中间状态
TaskStatus.Running 该任务正在运行,但尚未完成
TaskStatus.WaitingForChildrenToComplete 该任务已完成执行,正在隐式等待附加的子任务完成
3) 最终状态
TaskStatus.Canceled 该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的 CancellationToken 发出了信号。Task属性IsFaulted被设置为true
TaskStatus.Faulted 由于未处理异常的原因而完成的任务。Task属性IsCanceled被设置为true
TaskStatus.RunToCompletion 已成功完成执行的任务。Task属性IsCompleted被设置为true,IsFaulted和IsCanceled被设置为false
四、带返回值的Task
线程是一个执行过程,线程本身是没有返回值的概念。Task是运行在线程池线程上的一个任务。我们可以通过Task来得到执行后的结果。
Task.Run(); //无返回值
Task.Run<T>(); //有返回值
//直接执行方法
var task4 = Task.Run<string>(() => { return "返回值"; } );
Console.WriteLine("task4的返回值:" task4.Result); //返回值
//执行委托方法
var task5 = Task.Run<int>(() => { return Cal(1,2); });
Console.WriteLine("task5的返回值:" task5.Result); //3
static int Cal(int x, int y) => x y;
五、Task方法
1、Wait
task1.Wait();
阻塞直到Task完成,Task的初衷是并发,所以应尽量减少使用Wait
2、WaitAll
Task.WaitAll(task1, task2, task3); //阻塞主线程,直到所有task完成
3、WaitAny
只要有一个任务执行完成,将不再阻塞
4、CancellationTokenSource、CancellationToken 取消任务Task
当任务执行超长时间,可以取消任务
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
var task = Task.Run(()=> {
Thread.Sleep(2000);
if(token.IsCancellationRequested) //True
Console.WriteLine("task任务已取消!");
else
Console.WriteLine("task任务正常执行!");
},token);
Console.WriteLine("task状态:" task.Status); //TaittingToRun
Thread.Sleep(300);
cts.Cancel(); //取消任务
Thread.Sleep(3000);
Console.WriteLine("task状态:" task.Status); //RunToCompletion
5、ContinueWith 后续操作
task.ContinueWith(t => { Console.WriteLine("task任务已完成,状态:" t.Status); });
六、基于Task的异步编程
1、async,await
await和async必定是成对出现的!
当遇到await时,会把控制权交出给调用方。
static void Main(string[] args)
{
MyMain();
}
static async void MyMain()
{
Console.WriteLine("这是MyMain前");
Task1();
Console.WriteLine("这是MyMain后");
}
static async Task Task1()
{
Console.WriteLine("这是task1之前");
await Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("这是一秒后的task1");
});
Console.WriteLine("这是task1之后");
}
//执行结果:
这是MyMain前
这是task1之前 //往下遇到了await,把控制权交给调用方MyMain,Task1同步执行
这是MyMain后 //控制权在MyMain,继续往下执行
这是一秒后的task1
这是task1之后
2、WhenAny和WhenAll
我们知道WaitAny和WaitAll属性于是同步方法,会阻塞当前线程。而WhenAny和WhenAll却是异步的,而且是并行执行,可以缩短执行时间。
static void Main(string[] args)
{
Console.WriteLine("主线程Start ID:" Thread.CurrentThread.ManagedThreadId);
var watcher2 = new Stopwatch();
watcher2.Start();
var reader = ReadUser();
var lst = reader.Result;
watcher2.Stop();
Console.WriteLine("读取用户共用时:" watcher2.ElapsedMilliseconds / 1000 "秒");
foreach (var item in lst)
Console.WriteLine(item);
Console.WriteLine("主线程End ID:" Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000 * 20);
}
async static Task<List<string>> ReadUser()
{
Console.WriteLine("进入Task");
var lst = new List<string>();
lst.AddRange(await ReadUser1());
lst.AddRange(await ReadUser2());
return lst;
}
async static Task<List<string>> ReadUser1()
{
var lst = new List<string>();
await Task.Run(() =>
{
for (var i = 0; i < 5; i )
{
Thread.Sleep(500); //模拟在读数据库时的时间消耗
Console.WriteLine($"User-a-{i} ; ID:{Task.CurrentId} ThreadID:{Thread.CurrentThread.ManagedThreadId} Time:{DateTime.Now.ToString()}");
lst.Add("User-a-" i);
}
});
return lst;
}
async static Task<List<string>> ReadUser2()
{
var lst = new List<string>();
await Task.Run(() =>
{
for (var i = 0; i < 10; i )
{
Thread.Sleep(500); //模拟在读数据库时的时间消耗
Console.WriteLine($"User-b-{i} ; ID:{Task.CurrentId} ThreadID:{Thread.CurrentThread.ManagedThreadId} Time:{DateTime.Now.ToString()}");
lst.Add("User-b-" i);
}
});
return lst;
}
可以看出,这里执行了同步操作,ReadUser1花费了2秒多,ReadUser2花费了5秒多,一共是7秒多。
那怎么让他们异步并发执行呢,使只耗时5秒呢?
1)使用WhenAny
async static Task<List<string>> ReadUser()
{
Console.WriteLine("进入Task");
var lst = new List<string>();
var task1 = ReadUser1();
var task2 = ReadUser2();
var tasks = new List<Task> { task1, task2 };
while (tasks.Count>0)
{
var taskFinished = await Task.WhenAny(tasks);
if (taskFinished == task1)
{
Console.WriteLine("task1 已经完成");
lst.AddRange(task1.Result);
}
else if (taskFinished == task2)
{
Console.WriteLine("task2 已经完成");
lst.AddRange(task2.Result);
}
tasks.Remove(taskFinished);
}
return lst;
}
2)使用WhenAll
async static Task<List<string>> ReadUser()
{
Console.WriteLine("进入Task");
var lst = new List<string>();
var task1 = ReadUser1();
var task2 = ReadUser2();
var tasks = new List<Task> { task1, task2 };
await Task.WhenAll(tasks);
lst.AddRange(task1.Result);
lst.AddRange(task2.Result);
return lst;
}
实现的效果和上面的一样,但更为简洁。
3)When和Wait的区别
上述代码WhenAll换成WaitAll一样也会并发扫行,区别在于When不会阻塞主线程(前前是异步,有await),而Wait会