- Task诞生于.NETFramework 3.0,同时支持.NET Core
- Task被称为C# 中多线程的最佳实现
Task task = new Task(() =>
{
Console.WriteLine($"==================new Task start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss.fff")}========================");
DoSomething("new Task");
});
task.Start();
Task task = Task.Run(() =>
{
Console.WriteLine($"==================Task.Run start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss.fff")}========================");
DoSomething("Task.Run");
});
TaskFactory taskFactory = Task.Factory;
taskFactory.StartNew(()=>{
Console.WriteLine($"==================TaskFactory start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss.fff")}========================");
DoSomething("TaskFactory");
});
上面的私有方法:
///
/// 模拟比较消耗资源的方法
///
///
private void DoSomething(string name)
{
Console.WriteLine($"==================DoSomething start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss.fff")}========================");
long result = 0;
//循环一百万次
for (int i = 0; i < 1000_000; i++)
{
result += i;
}
//Thread.Sleep(2000);
Console.WriteLine($"==================DoSomething end {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss.fff")} {result}========================");
}
Task也是有线程池管理,并且开启的线程是可以被重复利用的
//设置线程池中最多有18个线程,全局唯一
ThreadPool.SetMaxThreads(18, 18);
List<Task> taskList = new List<Task>();
List<string> threadIds = new List<string>();
//循环二十次,理论上会开启二十个线程,但是因为设置了最大线程数,所以线程池中最多只能开启14个线程,而且可以重复使用
for (int i = 0; i < 20; i++)
{
taskList.Add(Task.Run(() =>
{
threadIds.Add(Thread.CurrentThread.ManagedThreadId.ToString("00"));
Console.WriteLine($"==================当前线程id为 {Thread.CurrentThread.ManagedThreadId.ToString("00")} ========================");
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine($"线程总数为:{threadIds.Distinct().Count()}");
父子线程就是线程里面开启线程
默认情况下,父线程不会等待子线程,父线程不阻塞
///
/// 父子线程
///
///
///
private void button2_Click(object sender, EventArgs e)
{
Console.WriteLine($"==================父子线程 start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}========================");
//父线程
Task task = Task.Run(() =>
{
Console.WriteLine($"+++++++++++++++父线程 task start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}+++++++++++++++");
//子线程1
Task task1 = Task.Run(() =>
{
Console.WriteLine($"子线程 task1 id: {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}");
Thread.Sleep(3000);
Console.WriteLine($"子线程task1 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}");
});
//子线程2
Task task2 = Task.Run(() =>
{
Console.WriteLine($"子线程 task2 id: {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}");
Thread.Sleep(3000);
Console.WriteLine($"子线程task2 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}");
});
//Thread.Sleep(3000);
Console.WriteLine($"+++++++++++++++父线程 task end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}+++++++++++++++");
});
//等待父线程执行完成
task.Wait(); //这里会阻塞主线程(卡界面)
Console.WriteLine($"==================父子线程 end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}========================");
}
分析:
01
开启一个父线程 task 04
,主(UI)线程01
等待父线程task 04
完成;task 04
线程中,代码执行从上到下,所以会先打印“父线程 task start”
;task1 05
和 task2 06
,但是这两个线程是异步的,父线程task 04
也没有等待这两个线程,所以此时输出:"父线程 task end"
;task1 05
和 task2 06
,所以会分别输出子线程id;01
已经运行完成;task 04
的时候秒其实是会卡顿界面的(主线程阻塞),为了效果更加明显,设置3秒休眠时间(但是上图的运行结果也会有所差异)如果父线程在等待的时候,也要等待子线程执行完毕,就需要用到线程附着:TaskCreationOptions.AttachedToParent
///
/// 线程附着
///
///
///
private void button3_Click(object sender, EventArgs e)
{
Console.WriteLine($"==================父子线程 start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}========================");
//开启父线程
Task task = new Task(() =>
{
Console.WriteLine($"+++++++++++++++父线程 task start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}+++++++++++++++");
//开启子线程1
Task task1 = new Task(() =>
{
Console.WriteLine($"子线程 task1 id: {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}");
Thread.Sleep(3000);
Console.WriteLine($"子线程task1 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}");
},TaskCreationOptions.AttachedToParent);
//开启子线程2
Task task2 = new Task(() =>
{
Console.WriteLine($"子线程 task2 id: {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}");
Thread.Sleep(3000);
Console.WriteLine($"子线程task2 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}");
},TaskCreationOptions.AttachedToParent);
task1.Start();
task2.Start();
//Thread.Sleep(3000); //休眠三秒,界面卡顿会更明显
Console.WriteLine($"+++++++++++++++父线程 task end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}+++++++++++++++");
});
task.Start();
//等待父线程,主线程(UI线程)会阻塞,造成界面卡顿
task.Wait();
Console.WriteLine($"==================父子线程 end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}========================");
}
注意事项:
运行结果:主线程会等待父线程执行完毕,再等待子线程执行完毕,最后主线程结束
TaskCreationOptions.PreferFairness
可以设置线程优先级,但这里的优先级只是提高了优先执行的概率,并不是绝对的优先。
///
/// 线程优先级
///
///
///
private void button4_Click(object sender, EventArgs e)
{
Console.WriteLine($"==================父子线程 start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}========================");
//开启线程
Task task1 = new Task(() =>
{
Console.WriteLine($"开启一个新的线程task1:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
Task task2 = new Task(() =>
{
Console.WriteLine($"开启一个新的线程task2:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}, TaskCreationOptions.PreferFairness);
task1.Start();
task2.Start();
等待线程,主线程(UI线程)会阻塞,造成界面卡顿
//task1.Wait();
//task2.Wait();
Console.WriteLine($"==================父子线程 end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}========================");
}
运行结果:可以看到,如果不设置优先级,task1肯定是先于task2执行的,但是给task2设置优先级之后,task2就有一定概率优先执行。
TaskCreationOptions.LongRunning
可以设置允许当前线程消耗大量的资源或长时间运行。
///
/// TaskCreationOptions.LongRunning:允许在子线程中消耗大量资源或者长时间运行
///
///
///
private void button5_Click(object sender, EventArgs e)
{
Console.WriteLine($"==================button5_Click start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}========================");
//开启线程
Task task = new Task(() =>
{
Console.WriteLine($"开启一个新的线程task1:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
},TaskCreationOptions.LongRunning); //在子线程中,如果消耗了大量的资源,默认是允许的,允许长时间在子线程中执行任务
task.Start();
等待线程,主线程(UI线程)会阻塞,造成界面卡顿
//task.Wait();
Console.WriteLine($"==================button5_Click end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")}========================");
}
不阻塞线程,一般不单独使用,Delay相当于子线程延时一段时间,然后完成某个动作
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine("Sleep之前");
Thread.Sleep(2000);
Console.WriteLine("Sleep之后");
stopwatch.Stop();
Console.WriteLine($"Sleep消耗时间:{stopwatch.ElapsedMilliseconds}");
}
//Sleep就是主线程等待了,Delay相当于子线程定时多久再去做某件事
{
//Delay相当于计时多久,然后做事
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine("Delay之前");
Task.Delay(2000).ContinueWith(s=> {
stopwatch.Stop();
Console.WriteLine($"Delay消耗时间:{stopwatch.ElapsedMilliseconds}");
Console.WriteLine("这是一个延时任务");
});
Console.WriteLine("Delay之后");
}
可以看到,打印Sleep之前
和Sleep之后
中间阻塞了2秒,但是打印Delay之前
和Delay之后
没有阻塞
在子线程中的Task.CurrentId与 task.Id其实是一样的,都是指当前任务id
{
//线程id和任务id
Task t1 = new Task(()=> {
Console.WriteLine($"This thread(t1) id is {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"This task(t1) id is {Task.CurrentId}");
});
t1.Start();
t1.Wait();
Console.WriteLine($"This thread(main thread) id is {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"This task(t1) id is {t1.Id}");
//线程id和任务id
Task t2 = new Task(() => {
Console.WriteLine($"This thread id(t2) is {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"This task(t2) id is {Task.CurrentId}");
});
t2.Start();
t2.Wait();
Console.WriteLine($"This thread(main thread) id is {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"This task(t2) id is {t2.Id}");
}
(1)现在有两个需求:
1、发起多个任务,任意任务完成,则继续执行主线程中的操作
2、发起多个任务,全部任务完成。则继续执行主线程中的操作
{
TaskFactory taskFactory = new TaskFactory();
taskFactory.StartNew(() =>{
DoSomething("task1");
});
taskFactory.StartNew(() => {
DoSomething("task2");
});
taskFactory.StartNew(() => {
DoSomething("task3");
});
}
如果按上面,执行结果为下图,明显不符合需求
因此需要用到WaitAny
和WaitAll
方法:
{
List<Task> tasks = new List<Task>();
TaskFactory taskFactory = new TaskFactory();
tasks.Add(taskFactory.StartNew(() =>{
DoSomething("task1");
}));
tasks.Add(taskFactory.StartNew(() => {
DoSomething("task2");
}));
tasks.Add(taskFactory.StartNew(() => {
DoSomething("task3");
}));
//等待任意任务完成,就往后继续执行,会阻塞主线程(卡界面)
Task.WaitAny(tasks.ToArray());
Console.WriteLine("任意任务完成即可执行!");
//等待全部任务完成,就往后继续执行,会阻塞主线程(卡界面)
Task.WaitAll(tasks.ToArray());
Console.WriteLine("全部任务完成后执行!");
}
比如做一个主页,主页上有很多模块,假设这些模块组成一个复杂对象,
在取数据的时候,不同的模块的数据来自不同的地方(如:数据库,内存,文件等),
此时就可以开启多个线程并行的取数,然后等待所有的模块的数据都获取到,
将所有模块数据组装成一个复杂对象返回到前端
(3)WaitAny
的应用场景
用淘宝举例:商品类目(同一商品)信息可能来自:缓存、数据库、第三方接口等,
那么在获取数据的时候,从这些途径的任一途径获取到数据就可以返回到页面展示了,
那么就可以发起多个线程分别从不同的途径获取数据,
任意一个线程获取到数据,就返回到页面,此时就可以用`WaitAny`
本质:回调
需求升级:
ContinueWhenAny
{
List<Task> tasks = new List<Task>();
TaskFactory taskFactory = new TaskFactory();
tasks.Add(taskFactory.StartNew(() =>{
DoSomething("task1");
}));
tasks.Add(taskFactory.StartNew(() => {
DoSomething("task2");
}));
tasks.Add(taskFactory.StartNew(() => {
DoSomething("task3");
}));
//任意任务完成,则继续执行主线程中的操作(不能阻塞主线程)
taskFactory.ContinueWhenAny(tasks.ToArray(), s =>
{
//这里可能是开启了新的线程,也可能是上面已经完成了的任务的线程,因为已经完成的任务的线程回到线程池可能会被(这个线程)重新使用,这样的线程叫做热线程
Console.WriteLine($"==============={Thread.CurrentThread.ManagedThreadId.ToString("00")} =================");
//Console.WriteLine("第一个任务完成,给与奖励!");
Console.WriteLine("任意任务完成后执行!");
});
}
//全部任务完成,则继续执行主线程中的操作(不能阻塞主线程)
taskFactory.ContinueWhenAll(tasks.ToArray(), s =>
{
//这里可能是开启了新的线程,也可能是上面已经完成了的任务的线程,因为已经完成的任务的线程回到线程池可能会被(这个线程)重新使用,这样的线程叫做热线程
Console.WriteLine($"==============={Thread.CurrentThread.ManagedThreadId.ToString("00")} =================");
Console.WriteLine("全部任务完成后执行!");
});
思考:开启线程的时候如何控制线程数量?循环n次就开启n个线程吗?
要求:1、不能通过ThreadPool设置
2、线程数控制在20个
{
List<Task> taskList = new List<Task>();
for (int i = 0; i < 100; i++)
{
int k = i;
//判断没有完成的线程数量是否大于20(限制线程数量最多为20个)
if (taskList.Count(p => p.Status != TaskStatus.RanToCompletion) > 20)
{
//如果未完成的线程数量大于20,则等待至少一个线程完成
Task.WaitAny(taskList.ToArray());
//重置taskList,将上面完成的线程去除,留下未完成的线程
taskList = taskList.Where(p => p.Status != TaskStatus.RanToCompletion).ToList();
}
//再新建一个线程
taskList.Add(Task.Run(() =>
{
Console.WriteLine($"当前是第{k}次循环,线程id为:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}));
}
}
任务并发:即任务可以并行
提高性能,改善用户体验:以资源换时间
多线程Task