C# Advanced Tutorial 1-13-Threads02


主要内容概要

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

  1. Task.WaitAll: 等待全部任务都完成
  2. Task.WaitAny: 数据源来自于不同的接口或者缓存,那就可以开启多个线程全部去获取数据,只要是有一个线程获取到数据了,其他的线程就不管了!
  3. 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不住了。


文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
C# Advanced Tutorial 1-14-Threads03 C# Advanced Tutorial 1-14-Threads03
主要内容概要1 多异常处理和线程取消2 多线程的临时变量3 线程安全和锁lock4 await async 多线程异常思考:多线程中如果某一个线程异常了,就会终结当前线程;对其他的线程是没有影响的;多线程中的异常去哪儿了? 被吞掉了。 t
下一篇 
C# Advanced Tutorial 1-12-Threads01 C# Advanced Tutorial 1-12-Threads01
主要内容概要线程池ThreadPool ThreadC#中的多线程 1.0已经存在Thread类: C#对线程对象的一个封装ThreadStart是一个没有参数没有返回值的委托:public delegate void ThreadStar
  目录