一、多线程的根源
定义:C#/.Net Thread ThreadPool Task await/async -- C# 是面向对象的语音,
Thread 就是一个封装,封装的计算机线程概念(一些 API)。
Thread.CurrentThread.ManagedThreadId:当前响应线程的 ID,没有什么意义,是 CLR 起的标识,不是系统的。
1、进程 Processor:
属于计算机概念(虚拟的),把一个程序运行时占用的全部计算资源(CPU、内存、磁盘、网络...),统称为进程。
2、线程 Thread:
也是计算机概念(虚拟的),是进程的最小执行流(任何一个操作的响应都需要执行流),线程也有自己的计算资源 -- 线程是依托于进程的
3、多线程 MultiThread:
多个执行流并发执行,就说多线程。
二、单线程与多线程
1、单线程卡界面,多线程不卡界面
2、单线程慢,多线程快
3、多线程无序性
单线程:
Debug.WriteLine(" ");
Debug.WriteLine("******** btnSingleThread_Click 同步方法 start {0}*******", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 5; i++)
{
string name = $"btnSingleThread_Click_{i}";
Action action = () => { this.DoSomethingLong(name); };
action.Invoke();//同步执行。
}
Debug.WriteLine("******** btnSingleThread_Click 同步方法 end {0}*******", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(" ");
多线程:
Debug.WriteLine(" ");
Debug.WriteLine("******** btnMulti_Click 同步方法 start {0}*******", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 10; i++)
{
string name = $"btnMulti_Click_{i}";
Action action = () => { this.DoSomethingLong(name); };
//action.Invoke();//同步执行。
Task.Run(action);
}
Debug.WriteLine("******** btnMulti_Click_ 同步方法 end {0}*******", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(" ");
Q:单线程卡界面,多线程不卡界面
A1:线程就是执行流,卡界面就表示线程正在忙碌,忙于 DoSomething ,所以界面卡住了 UI 线程(主线程)。
A2:UI 线程闲置了,DoSomething 交给了子线程(其他线程 Task.Run)。
应用:发短信,支付,上传文件。直接用子线程完成,主线程先返回。
Q:单线程慢,多线程快
A1:多线程是多个人干活,但需要更多的资源,多线程本质是资源换性能。
线程数不成正比
CPU 不够 -- CPU 只有这么多,线程多了是没有意义的。
调度机制 -- 线程切换。CPU 分片、线程调度、上下文切换都需要成本。
注意:线程不是越多越好。Task 比较好,是基于 ThreadPoll 开发的。线程的数量在 CPU 核数*3 (默认线程池有 2048个线程)以内比较合适。
应用:需要提升处理速度的地方可以考虑多线程。
多线程无序性:
因为线程是计算机资源,.Net 无法控制。因为 CPU 调度策略,启动与完成时是无序的。
三、多线程的写法
任何的多线程,都离不开委托 delegate,最佳实现是 Task 写法。
委托:是同步执行的
多线程:多个执行流,多线程并发。
1、Thread
2、ThreadPool
3、BeginInvoke
4、Task
5、Parallel
6、await/async
四、Task
1、Task 多线从场景实战
2、线程顺序掌握,多 API 灵活应用
任务能拆分、能并行的才可以多线程。
阻塞式:卡 UI
Debug.WriteLine(" ");
Debug.WriteLine("******** btnAdvanced_Click start {0}*******", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine("准备 1");
Debug.WriteLine("准备 2");
Debug.WriteLine("准备 3");
Debug.WriteLine("准备 4");
Debug.WriteLine("准备 5");
List<Task> taskList = new List<Task>();
taskList.Add(Task.Run(() => { Coding("Leo", "SQLServer"); }));
taskList.Add(Task.Run(() => { Coding("Chaw", "C#"); }));
taskList.Add(Task.Run(() => { Coding("Joanna", "Java"); }));
taskList.Add(Task.Run(() => { Coding("Bob", "WeChat"); }));
Task.WaitAny(taskList.ToArray());//阻塞式:等待 tasklist 中任一个完成后,会造成 UI 卡死。因为主线程在等待
//需要 tasklist 完成后才能执行
Task.WaitAll(taskList.ToArray());//阻塞式:等待 tasklist 完成后,会造成 UI 卡死。因为主线程在等待
Debug.WriteLine("完成交钱 5");
Debug.WriteLine(" ");
Debug.WriteLine("******** btnAdvanced_Click end {0}*******", Thread.CurrentThread.ManagedThreadId);
非阻塞式:不卡 UI,但能按顺序完成。
再起一个 Task.Run,把阻塞任务放在其他线程,所以主线程 UI,不会卡。【不推荐,不要乱包】
List<Task> taskList = new List<Task>();
taskList.Add(Task.Run(() => { Coding("Leo", "SQLServer"); }));
taskList.Add(Task.Run(() => { Coding("Chaw", "C#"); }));
taskList.Add(Task.Run(() => { Coding("Joanna", "Java"); }));
taskList.Add(Task.Run(() => { Coding("Bob", "WeChat"); }));
//把阻塞等待任务交给其他线程
Task.Run(() =>
{
Task.WaitAny(taskList.ToArray());//阻塞式:等待 tasklist 中任一个完成后,会造成 UI 卡死。因为主线程在等待
Debug.WriteLine("完成交钱 30%");
//需要 tasklist 完成后才能执行
Task.WaitAll(taskList.ToArray());//阻塞式:等待 tasklist 完成后,会造成 UI 卡死。因为主线程在等待
Debug.WriteLine("完成交钱 50%");
});
Debug.WriteLine(" ");
Debug.WriteLine("******** btnAdvanced_Click end {0}*******", Thread.CurrentThread.ManagedThreadId);
推荐使用此方法解决阻塞问题:
List<Task> taskList = new List<Task>();
taskList.Add(Task.Run(() => { Coding("Leo", "SQLServer"); }));
taskList.Add(Task.Run(() => { Coding("Chaw", "C#"); }));
taskList.Add(Task.Run(() => { Coding("Joanna", "Java"); }));
taskList.Add(Task.Run(() => { Coding("Bob", "WeChat"); }));
taskList[0].ContinueWith(t => Debug.WriteLine("Leo 完成"));
Task.Factory.ContinueWhenAny(taskList.ToArray(), t => Debug.WriteLine("有一个完成"));
Task.Factory.ContinueWhenAll(taskList.ToArray(), t => Debug.WriteLine("全部完成"));
3、临时变量 - 线程异常 - 线程取消
a、临时变量:
for (int i = 0; i < 10; i++)
{
string name = $"btnMulti_Click_{i}";
//Action action = () => { this.DoSomethingLong(name); };
Action action = () => { this.DoSomethingLong($"btnMulti_Click_{i}"); };
//action.Invoke();//同步执行。
Task.Run(action);
}
多线程问题:以下两者是不一样的。因为执行时机与上下文的问题。
前者:先计算出了 i 后才开始执行线程,使用的是 name 参数,已经绑定好了。
后者:线程执行时,才调用代入 i,此时 i 是变化的。
Action action = () => { this.DoSomethingLong(name); };
Action action = () => { this.DoSomethingLong($"btnMulti_Click_{i}"); };
4、多方案线程安全和任务分配
均匀的分配任务:
当线程超过 27 ,等有任务完成再分配任务。