那么什么时候能用多线程? 任务能并发的时候
多线程能干嘛?提升速度/优化用户体验
网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll 列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了
现实实例
多人合作开发---多线程--提升效率/性能
1 {
2 TaskFactory taskFactory = new TaskFactory();
3 List<Task> taskList = new List<Task>();
4 taskList.Add(taskFactory.StartNew(o=> Coding("A", " Portal"), "A"));
5 taskList.Add(taskFactory.StartNew(o=> Coding("B", " DBA"), "B"));
6 taskList.Add(taskFactory.StartNew(o=> Coding("C", " Client"), "C"));
7 taskList.Add(taskFactory.StartNew(o=> Coding("D", "Service"), "D"));
8 taskList.Add(taskFactory.StartNew(o=> Coding("E", " Wechat"), "E"));
9
10 //谁第一个完成,获取一个红包奖励
11 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
12 //实战作业完成后,一起庆祝一下
13 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));
14 //ContinueWhenAny ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程
15
16
17 //阻塞当前线程,等着任意一个任务完成
18 Task.WaitAny(taskList.ToArray());//也可以限时等待
19 Console.WriteLine("准备环境开始部署");
20 //需要能够等待全部线程完成任务再继续 阻塞当前线程,等着全部任务完成
21 Task.WaitAll(taskList.ToArray());
22 Console.WriteLine("5个模块全部完成后,集中调试");
23
24 //Task.WaitAny WaitAll都是阻塞当前线程,等任务完成后执行操作
25 //阻塞卡界面,是为了并发以及顺序控制
26 }
View Code
1 /// <summary>
2 /// 模拟Coding过程
3 /// </summary>
4 /// <param name="name"></param>
5 /// <param name="projectName"></param>
6 private static string Coding(string name, string projectName)
7 {
8 Console.WriteLine($"****************Coding Start {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
9 long lResult = 0;
10 for (int i = 0; i < 1_000_000_000; i++)
11 {
12 lResult += i;
13 }
14 Console.WriteLine($"****************Coding End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
15 return name;
16 }
View Code
多线程异常处理
1 #region 多线程异常处理
2 {
3 try
4 {
5
6 List<Task> taskList = new List<Task>();
7 for (int i = 0; i < 100; i++)
8 {
9 string name = $"btnThreadCore_Click_{i}";
10 taskList.Add(Task.Run(() =>
11 {
12 if (name.Equals("btnThreadCore_Click_11"))
13 {
14 throw new Exception("btnThreadCore_Click_11异常");
15 }
16 else if (name.Equals("btnThreadCore_Click_12"))
17 {
18 throw new Exception("btnThreadCore_Click_12异常");
19 }
20 else if (name.Equals("btnThreadCore_Click_38"))
21 {
22 throw new Exception("btnThreadCore_Click_38异常");
23 }
24 Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
25 }));
26 }
27 //多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;
28 //那线程异常哪里去了? 被吞了,
29 //假如我想获取异常信息,还需要通知别的线程
30 Task.WaitAll(taskList.ToArray());//1 可以捕获到线程的异常
31 }
32 catch (AggregateException aex)//2 需要try-catch-AggregateException
33 {
34 foreach (var exception in aex.InnerExceptions)
35 {
36 Console.WriteLine(exception.Message);
37 }
38 }
39 catch (Exception ex)//可以多catch 先具体再全部
40 {
41 Console.WriteLine(ex);
42 }
43 //线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要线程取消
44 //工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作
45 }
46 #endregion
View Code
多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;线程异常哪里去了? 被吞了
多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息 ,通知别的线程
线程取消
1 {
2 CancellationTokenSource cts = new CancellationTokenSource();
3 var token = cts.Token; cts.Cancel();
4 CancellationTokenSource cts2 = new CancellationTokenSource();
5 var token2 = cts2.Token;
6 List<Task> taskList = new List<Task>();
7 for (int i = 0; i < 10; i++)
8 {
9 int k = i;
10 switch (i%5)
11 {
12 case 0:
13 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=0"); }));break;
14 case 1:
15 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=1"); },token)); break;
16 case 2:
17 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=2"); }, token2)); break;
18 case 3:
19 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=3"); })); break;
20 case 4:
21 taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=4");
22 throw new Exception("throw new Exception");
23 })); break;
24 }
25 }
26 //Thread.Sleep(500);
27 cts2.Cancel();
28 try
29 {
30 Task.WaitAll(taskList.ToArray());
31 }catch(AggregateException ae)
32 {
33 foreach (var item in ae.InnerExceptions)
34 {
35 Console.WriteLine($"{item.GetType().Name}:{item.Message}");
36 }
37 }
38 Console.WriteLine("**********************************");
39 foreach (var item in taskList)
40 {
41 Console.WriteLine($"Id:{item.Id},Status:{item.Status}");
42 if (item.Exception != null)
43 {
44 foreach (var ex in item.Exception.InnerExceptions)
45 {
46 Console.WriteLine($"{ex.GetType().Name}:{ex.Message}");
47 }
48 }
49 }
50 }
View Code
运行上面的代码,有四个任务被取消,取消注释,则有两个任务被取消
线程安全
如果你的代码在进程中有多个线程同时运行这一段,如果每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的 线程安全问题一般都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要多线程都能访问和修改
Lock
1、Lock解决多线程冲突
Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着
推荐锁是private static readonly object
A 不能是Null,可以编译不能运行;
B 不推荐lock(this),外面如果也要用实例,就冲突了
1 public class LockHelper
2 {
3 public void Show()
4 {
5 LockTest test = new LockTest();
6 Console.WriteLine(DateTime.Now);
7 Task.Delay(10).ContinueWith(t =>
8 {
9 lock (test)
10 {
11 Console.WriteLine($"*********Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
12 Thread.Sleep(5000);
13 Console.WriteLine($"*********End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
14 }
15 });
16 test.LockThis();
17 }
18 }
19 public class LockTest
20 {
21 private int lockthis;
22 public void LockThis()
23 {
24 lock (this)
25 //递归调用,lock this 会不会死锁? 不会死锁!
26 //这里是同一个线程,这个引用就是被这个线程所占据
27 {
28 Thread.Sleep(1000);
29 this.lockthis++;
30 if (this.lockthis < 10)
31 this.LockThis();
32 else
33 Console.WriteLine($"This is {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
34 }
35 }
36 }
View Code
这里LockThis自身递归调用不会死锁,这个引用被当前线程占用,但当另外的实例要使用时就冲突了,必须等待LockThis执行完成后,释放当前实例,外面的实例才能被调用
C 不应该是string; string在内存分配上是重用的,会冲突
1 {
2 LockTest test = new LockTest();
3 Console.WriteLine(DateTime.Now);
4 string lockString = "lockString";
5 Task.Delay(1000).ContinueWith(t =>
6 {
7 lock (lockString)
8 {
9 Console.WriteLine($"****lockString Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
10 Thread.Sleep(5000);
11 Console.WriteLine($"****lockString End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
12 }
13 });
14 test.LockString();
15 }
16 public class LockTest
17 {
18 private int lockthis;
19 public void LockThis()
20 {
21 lock (this)
22 //递归调用,lock this 会不会死锁? 不会死锁!
23 //这里是同一个线程,这个引用就是被这个线程所占据
24 {
25 Thread.Sleep(1000);
26 this.lockthis++;
27 if (this.lockthis < 10)
28 this.LockThis();
29 else
30 Console.WriteLine($"This is {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
31 }
32 }
33
34 private string lockString= "lockString";
35 public void LockString()
36 {
37 lock (lockString)
38 {
39 Thread.Sleep(2000);
40 Console.WriteLine($"This is {nameof(LockString)}:{this.lockString} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
41 Thread.Sleep(2000);
42 }
43 }
44 }
View Code
String类型在内存分配上按享元模式设计的,某个字符串被占用,其他线程就必须等待字符串释放后才能使用
D Lock里面的代码不要太多,这里是单线程的
2、线程安全集合
System.Collections.Concurrent.ConcurrentQueue<T>
3、 数据分拆,避免多线程操作同一个数据;又安全又高效
1 private int _sync = 0;
2 private int _async = 0;
3 private List<int> listInt = new List<int>();
4 private static readonly object lockObject = new object();
5 public void LockObject()
6 {
7 for (int i = 0; i < 1000; i++)
8 {
9 this._sync++;
10 }
11 for (int i = 0; i < 1000; i++)
12 {
13 Task.Run(() => this._async++);
14 }
15 for (int i = 0; i < 1000; i++)
16 {
17 int k = i;
18 Task.Run(() => this.listInt.Add(k));
19 }
20 Thread.Sleep(5 * 1000);
21 Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
22 }
View Code
运行上面的代码发现_sync=1000 _async与listInt集合个数都少于1000
1 public void LockObject()
2 {
3 for (int i = 0; i < 1000; i++)
4 {
5 this._sync++;
6 }
7 for (int i = 0; i < 1000; i++)
8 {
9 Task.Run(() => {
10 lock (lockObject)
11 {
12 this._async++;
13 }
14 });
15 }
16 for (int i = 0; i < 1000; i++)
17 {
18 int k = i;
19 Task.Run(() => {
20 lock (lockObject)
21 {
22 this.listInt.Add(k);
23 }
24 });
25 }
26 Thread.Sleep(5 * 1000);
27 Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
28 }
View Code
使用lock包装后 _async与listInt集合个数都为1000, 使用lock后 只有一个线程才能进入lock方法块内,相当于把程序又变回了单线程
微软文档:
lock:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/lock-statement
CancellationTokenSource:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtokensource?view=netframework-4.8
CancellationToken:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8 |