1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.Linq;
5 using System.Text;
6 using System.Threading;
7 using System.Threading.Tasks;
8
9 namespace AsyncTask
10 {
11 class Program
12 {
13 /*
14 前台线程和后台线程的区别
15 应用程序的主线程以及使用Thread构造的线程都默认为前台线程
16 使用Thread建立的线程默认情况下是前台线程,在进程中,只要有一个前台线程未退出,进程就不会终止.
17 主线程就是一个前台线程。而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止.
18 一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序
19 */
20
21 /*
22 * 异步和多线程有什么区别
23 * https://www.cnblogs.com/gbat/articles/6385182.html
24 * 这个网址说的还可以
25 *
26 *
27 *
28 异步多线程:什么时候用多线程? 提升速度==>可以并发运行为前提,不要强行拆分
29 例子:一个数据库,多条语句执行,即使拆分开了,还是要一条一条执行;; 监控执行监控的,高空抛物执行高空抛物的
30
31 场景1:项目经理 制定好 项目计划, 分发任务, 但是项目经理还要和甲方说 可以验收了,
32 如果项目经理属于一个线程, 其他人属于一个线程, 那么
33 告知甲方可以验收就会在编码前边
34 这怎么搞?
35
36
37 whenall whenany和waitall waitany
38 when这一类得如果说是不需要给用户反馈的话,就可以用(比如写个日志,发个邮件啥得)
39 wait这一类得话,可以是给用户一个反馈得,有些时候必须要阻塞一下
40
41 自己想得:when这一类得话,可以用ContinueWith做回调, 也可以给用户反馈
42 */
43
44
45 //加锁后
46 //private 防止外边的也去lock static 全场唯一 readonly 不能改动 object 表示引用
47 private static readonly object myLock = new Object();//微软官方推荐
48 static void Main(string[] args)
49 {
50 #region 第一部分 Task.Run();
51 //Console.WriteLine($"项目经理启动了一个项目:==={Thread.CurrentThread.ManagedThreadId.ToString()}===");
52 //Console.WriteLine($"项目经理分发任务:==={Thread.CurrentThread.ManagedThreadId.ToString()}===");
53
54
55 ////创建线程容器,把编码线程放进去,加入阻塞
56 //List tasks = new List();
57 ////开启新线程
58 //tasks.Add(Task.Run(() => Console.WriteLine($"夏侯惇*****收到任务开始编程:++++{Thread.CurrentThread.ManagedThreadId.ToString()}++++")));
59 //tasks.Add(Task.Run(() => Console.WriteLine($"曹仁*****收到任务开始编程:++++{Thread.CurrentThread.ManagedThreadId.ToString()}++++")));
60 //tasks.Add(Task.Run(() => Console.WriteLine($"张辽*****收到任务开始编程:++++{Thread.CurrentThread.ManagedThreadId.ToString()}++++")));
61 //tasks.Add(Task.Run(() => Console.WriteLine($"徐晃*****收到任务开始编程:++++{Thread.CurrentThread.ManagedThreadId.ToString()}++++")));
62
63 ////Task.WaitAny(tasks.ToArray());//这里会阻塞线程,等待 =某= 个任务完成后,就会进入下一行 卡界面
64 ////Console.WriteLine($"这里有奖品,先到先得");
65 ////Task.WaitAll(tasks.ToArray(),1000);//这里会等待子线程执行一秒钟 ,一秒以后会继续执行,如果一秒内执行完了,OK继续,这里意思是最多等待1秒
66 ////Console.WriteLine("1秒以后,我会继续执行");
67
68 ///*多线程加快速度 ,但是全部任务完成以后才可以执行下面这个验收 那么主线程(经理线程就要 阻塞一下)*/
69 //Task.WaitAll(tasks.ToArray());//这里会阻塞当前线程,等着全部任务完成后, 再进行下一行 会卡界面
70 //Console.WriteLine($"项目经理告知甲方开始验收项目了!!!!!:==={Thread.CurrentThread.ManagedThreadId.ToString()}===");
71
72
73 /*如果想要不卡界面怎么整呢
74 ContinueWith 做一个延续,其实就是个回调函数, 当前面得任务完成后, 直接就做接下来得动作
75 */
76 //Task.WhenAny(tasks.ToArray()).ContinueWith(m => Console.WriteLine("用我可以不用等待,不回卡界面,我是某个完成任务,就可以"));
77 //Task.WhenAll(tasks.ToArray()).ContinueWith(m=>Console.WriteLine("用我可以不用等待,不回卡界面,我是全部执行完的任务"));
78 #endregion
79
80 #region 第二部分 模拟执行10000个任务,但是只有10个线程
81 /*
82 模拟执行10000个任务,但是只有10个线程
83
84 */
85 //模拟任务数量
86 //List list = new List();
87 //for (int i = 0; i < list.Count; i++)
88 //{
89 // list.Add(i);
90 //}
91 ////模拟任务
92 //Action action = i =>
93 //{
94 // Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
95 // Thread.Sleep(new Random().Next(100,300));
96 //};
97
98 //List tasks = new List();
99
100 //foreach (var item in list)
101 //{
102 // int k = item;
103 // tasks.Add(Task.Run(()=>action.Invoke(k)));//开启线程任务,并封装近Task集合中
104 // //判断线程执行数量
105 // if (tasks.Count>10)//>10说明至少10个了,
106 // {
107 // /*
108 // 一旦超过10个线程任务,那就得在这等着,等着玩意有任务完成了,那么就将已经完成得清理掉,进行下一次循环
109 // */
110 // Task.WaitAny(tasks.ToArray());//有一个执行完就,就开始下一步
111 // tasks = tasks.Where(t=>t.Status!=TaskStatus.RanToCompletion).ToList();//将已经完成的进行过滤,
112 // }
113 //}
114 //Task.WaitAll(tasks.ToArray());
115
116
117
118 #endregion
119
120 #region 第三部分 Task.Delay 和 Thread.Sleep() 的区别
121 /*
122 * Task.Delay 和 Thread.Sleep() 的区别?
123 Task.Delay不会卡界面,不会让用户等待,可以继续操作, 过一段时间后,才开始反馈信息
124 Thread.Sleep() 会卡界面, 会让用户等待, 不可以继续操作, 只有等睡足了,可开始反馈
125 */
126
127 Stopwatch stopwatch = new Stopwatch();//计时器
128 //方式1
129 //stopwatch.Start();//开始计时
130 //Thread.Sleep(2000);//睡觉2秒
131 //stopwatch.Stop();
132 //Console.WriteLine(stopwatch.ElapsedMilliseconds);
133
134 //方式2
135 //Stopwatch stopwatch = new Stopwatch();//计时器
136 //stopwatch.Start();//开始计时
137 //Task.Delay(2000).ContinueWith(t =>
138 //{
139 // stopwatch.Stop();
140 // Console.WriteLine(stopwatch.ElapsedMilliseconds);
141 //});
142
143 //方式3
144 //stopwatch.Start();//开始计时
145 //Task.Run(()=> {
146 // Thread.Sleep(2000);//睡觉2秒
147 // stopwatch.Stop();
148 // Console.WriteLine(stopwatch.ElapsedMilliseconds);
149 //});
150 #endregion
151
152 #region 第四部分 parallel 控制并发量, 比上边的第二部分更简洁
153
154 /*
155 要求:最多3个线程, 并发执行10个任务
156
157 */
158 //ParallelOptions parallelOptions = new ParallelOptions();//这个可以控制并发数量(即线程数量)
159 //parallelOptions.MaxDegreeOfParallelism = 3;//最多3个线程的要求 在这里可以实现
160 //Parallel.For(0,10,parallelOptions,i=>Console.WriteLine("高薪就业"));
161
162 /*
163 上边的要卡线程,卡屏,那么要不卡咋办呢?? 包一层呗
164 */
165 //Task.Run(()=> {
166 // ParallelOptions parallelOptions = new ParallelOptions();//这个可以控制并发数量(即线程数量)
167 // parallelOptions.MaxDegreeOfParallelism = 3;//最多3个线程的要求 在这里可以实现
168 // Parallel.For(0, 10, parallelOptions, i => Console.WriteLine("高薪就业"));
169 //});
170 #endregion
171
172 #region 第五部分 临时变量
173 //for (int i = 0; i < 5; i++)
174 //{
175 // Task.Run(()=>{
176 // Thread.Sleep(1000);
177 // Console.WriteLine(i);
178 // });
179 //}
180 /*
181 分析上边的输出情况: 应该是每隔1秒钟,输出一个数字分别是 0,1,2,3,4
182 但是实际执行结果确实55555......理想丰满,显示骨感,,,怎么回事>>>????
183
184 首先分析为什么打印出来全是5????
185 因为计算机的执行速度是很快的,,,我再sleep(1000)后,i早就变成5了 所以会打印出5,
186 假如没有sleep也会是5怎么解释?还是太快了,开线程的同时,早就i变为5了
187 因为全程只有1个i;
188 想要打印出01234的效果要做如下操作: 这样会有5个k,那么就可以打印出5个不同的数字了
189 */
190 //for (int i = 0; i < 5; i++)
191 //{
192 // int k = i;
193 // Task.Run(() => {
194 // Thread.Sleep(1000);
195 // Console.WriteLine(k);
196 // });
197 //}
198 #endregion
199
200
201 #region 第六部分 线程安全 lock 重点
202 /*概念刨析:
203 共有变量: 都能访问局部变量/全局变量/数据库得一个值/硬盘文件
204 线程内部不共享是 安全的 ; 共享就会有问题
205
206 上边说明了个什么问题??
207 线程安全的问题:
208 1.如果所有线程都能访问到,那么这个变量或者实例就是线程不安全的,会发生丢包
209 2.如果 变量是在线程内部, 那么就是线程安全的 不会发生丢包
210 也有坏处:============
211 虽然解决了丢包的问题 , 但是就不能并发了 因为每次只能一个线程去执行 . 牺牲了性能 要尽量缩小lock的范围
212 */
213 //例子:
214
215
216 List taskList = new List();
217 List<int> intList = new List<int>();
218 int totalCount = 0;
219 //for (int i = 0; i < 10000; i++)
220 //{
221 // int newI = i;
222 // taskList.Add(Task.Run(()=> {
223 // totalCount += 1;
224 // intList.Add(newI);
225
226 // }));
227 //}
228 Console.WriteLine("=======加锁前后=======");
229
230 for (int i = 0; i < 10000; i++)
231 {
232 int newI = i;
233 taskList.Add(Task.Run(() =>
234 {
235 /*
236 lock后的方法快,任意时刻只能有一个线程进入
237 锁只能锁引用类型, 占用这个引用类型的 引用连接 但是不要用string 因为享元
238 */
239 lock (myLock)// 加锁后,会输出完整的数
240 {
241 totalCount += 1;
242 intList.Add(newI);
243 }
244
245
246 }));
247
248 }
249
250 Console.WriteLine(totalCount);//输出9995
251 Console.WriteLine(intList.Count);//输出9996 说明这2个都会丢包
252 #endregion
253 Console.ReadKey();
254 }
255 }
256 }