主要内容概要
1 Task:Waitall WaitAny Delay
2 TaskFactory:ContinueWhenAny ContinueWhenAll
3 Parallel
Task
Task 是.NetFramework3.0出现的。提供了非常多的Api。
Task里面的线程是来自于线程池!
可以有下面几种写法:
Task task = new Task(() =>
{
this.DoSomethingLong("btntask_Click1");
});
task.Start();//开启了一个新的线程
Task task=Task.Run(() =>
{
this.DoSomethingLong("btntask_Click2");
});
TaskFactory taskFactory = Task.Factory;
taskFactory.StartNew(() =>
{
this.DoSomethingLong("btntask_Click3");
});
异步等待
Thread.Sleep()同步等待,阻塞当前线程:
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Thread.Sleep(2000);
stopwatch.Stop();
Console.WriteLine($"{stopwatch.ElapsedMilliseconds}");
Delay异步等待,不会阻塞主线程:
// Task.Delay 出现于4.5版本
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Task task = Task.Delay(2000).ContinueWith(t => //任务在2000ms 以后执行
{
stopwatch.Stop();
Console.WriteLine($"{stopwatch.ElapsedMilliseconds}");
Console.WriteLine("回调已完成");
});
思考:什么时候使用多线程?多线程能干嘛?
任务能并发的时候,提升速度,优化用户体验。
下面看一个例子,有几个动作:
上课方法:
private void Teach(string lession)
{
Console.WriteLine($"****************课程:{lession} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} ***************");
//Thread.Sleep(2000);
Console.WriteLine($"****************课程:{lession} End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} ***************");
}
Coding动作:
private void Coding(string name, string projectName)
{
Console.WriteLine($"****************{name}Coding Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} ***************");
int iResult = 0;
for (int i = 0; i < 100_000_000; i++)
{
iResult += i;
}
Console.WriteLine($"****************{name} Coding End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} ***************");
}
然后模拟上课,coding这个过程:
Console.WriteLine("老师开始讲课!");
Teach("Lesson1");
Teach("Lesson2");
Teach("Lesson3");
Teach("Lesson4");//这里不能并发,不能使用多线程
Console.WriteLine("课程结束,开始实战项目练习!但是实战项目内容比较复杂,需要合作完成!");
List<Task> tasksList = new List<Task>();
TaskFactory taskFactory = new TaskFactory();
tasksList.Add(taskFactory.StartNew(() => { this.Coding("杰克", "系统管理"); }));
tasksList.Add(taskFactory.StartNew(() => { this.Coding("流光易逝", "部门管理"); }));
tasksList.Add(taskFactory.StartNew(() => { this.Coding("偏执", "客户管理"); }));
tasksList.Add(taskFactory.StartNew(() => { this.Coding("清茶", "接口管理"); }));
tasksList.Add(taskFactory.StartNew(() => { this.Coding("秋陌", "写Api"); }));
//如果有一个同学完成了某一个模块,老师就需要准备环境!
// 等待某一个线程执行完毕以后 继续往后执行
Task.WaitAny(tasksList.ToArray());
Console.WriteLine("Richard老师开始准备环境部署项目!");
//Richard老师要等待大家都完成了以后,开始给点评!
Task.WaitAll(tasksList.ToArray()); //阻塞主线程
Console.WriteLine("5个模块均已完成,老师点评!");
taskFactory.ContinueWhenAll(tasksList.ToArray(),rArrary => Console.WriteLine($"开发完成,庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
WaitAny和WaitAll都是阻塞线程的,还可以加上参数时间。
这个过程中我们摄像一个场景:
在一个主页中,A —接口1 B—接口2 C—接口3
- Task.WaitAll: 等待全部任务都完成
- Task.WaitAny: 数据源来自于不同的接口或者缓存,那就可以开启多个线程全部去获取数据,只要是有一个线程获取到数据了,其他的线程就不管了!
- ContinueWhenAll和ContinueWhenAny不会阻塞线程 相当于回调函数,而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能的是主线程。
ContinueWith相当于回调函数,但是这两个线程是不一样的。
Task.Run(()=>this.DoSomethingLong("btnTask_Click")).ContinueWith(t=>Console.WriteLine($"btnTask_Click 已经完成 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
带有返回值的Task,获取Result的时候才会阻塞执行。
Task<int> result = Task.Run<int>(() =>
{
Thread.Sleep(2000);
return DateTime.Now.Year;
});
int i=result.Result;//会阻塞
如果不想阻塞,上面这种写法还可以这样优化一下:
Task.Run(()=>
{
int i=result.Result;//非阻塞的
});
Task.Run<int>(()=>
{
}).ContinueWith(tInt=>
{
int i=tInt.Result;
});
Parallel
Parallel 对Task进一步进行了封装 .Netframework 4.5版本出来。
多线程中控制执行顺序,一直是一个难题,使用Parallel可以控制线程的执行顺序。
Parallel并发执行了五个委托,开启了新线程,主线程参与计算,界面会阻塞。
效果等同于 Task WaitAll + 主线程。
1.接受多个Action委托
Parallel.Invoke(() => { this.DoSomethingLong("btnParallel_Click_1"); },
() => { this.DoSomethingLong("btnParallel_Click_2"); },
() => { this.DoSomethingLong("btnParallel_Click_3"); },
() => { this.DoSomethingLong("btnParallel_Click_4"); },
() => { this.DoSomethingLong("btnParallel_Click_5"); });
2.For循环来操作
Parallel.For(0, 10, parallelOptions,t => this.DoSomethingLong($"btnParallel_Click_{t}"));
3.Foreach 循环来操作
Parallel.ForEach(new int[] { 12, 13, 14, 15, 16, 17 }, parallelOptions, t => this.DoSomethingLong($"btnParallel_Click_{t}"));
控制线程数量:
控制线程数量,控制执行数量。
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 2;
Parallel.For(0, 10, parallelOptions,t => this.DoSomethingLong($"btnParallel_Click_{t}"));
有没有办法可以不阻塞执行这个For呢,不让主线程参与呢?直接再开一个线程来执行。
Task.Run(()=>
{
Parallel.For(0, 10, parallelOptions,t => this.DoSomethingLong($"btnParallel_Click_{t}"));
})
几乎90%以上的场景都可以用Task解决,如果不行,就要梳理一下流程;
建议不要线程嵌套,两层勉强能懂,三层就hold不住了。