C# 多线程总结 异常处理 线程取消 锁(lock)
那么什么时候能用多线程? 任务能并发的时候
多线程能干嘛?提升速度/优化用户体验
网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll
列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了
现实实例
多人合作开发---多线程--提升效率/性能
{
TaskFactory taskFactory = new TaskFactory();
List<Task> taskList = new List<Task>();
taskList.Add(taskFactory.StartNew(o=> Coding("A", " Portal"), "A"));
taskList.Add(taskFactory.StartNew(o=> Coding("B", " DBA"), "B"));
taskList.Add(taskFactory.StartNew(o=> Coding("C", " Client"), "C"));
taskList.Add(taskFactory.StartNew(o=> Coding("D", "Service"), "D"));
taskList.Add(taskFactory.StartNew(o=> Coding("E", " Wechat"), "E"));
//谁第一个完成,获取一个红包奖励
taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("")}"));
//实战作业完成后,一起庆祝一下
taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("")}")));
//ContinueWhenAny ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程
//阻塞当前线程,等着任意一个任务完成
Task.WaitAny(taskList.ToArray());//也可以限时等待
Console.WriteLine("准备环境开始部署");
//需要能够等待全部线程完成任务再继续 阻塞当前线程,等着全部任务完成
Task.WaitAll(taskList.ToArray());
Console.WriteLine("5个模块全部完成后,集中调试");
//Task.WaitAny WaitAll都是阻塞当前线程,等任务完成后执行操作
//阻塞卡界面,是为了并发以及顺序控制
}
/// <summary>
/// 模拟Coding过程
/// </summary>
/// <param name="name"></param>
/// <param name="projectName"></param>
private static string Coding(string name, string projectName)
{
Console.WriteLine($"****************Coding Start {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
long lResult = ;
for (int i = ; i < 1_000_000_000; i++)
{
lResult += i;
}
Console.WriteLine($"****************Coding End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
return name;
}
多线程异常处理
#region 多线程异常处理
{
try
{ List<Task> taskList = new List<Task>();
for (int i = ; i < ; i++)
{
string name = $"btnThreadCore_Click_{i}";
taskList.Add(Task.Run(() =>
{
if (name.Equals("btnThreadCore_Click_11"))
{
throw new Exception("btnThreadCore_Click_11异常");
}
else if (name.Equals("btnThreadCore_Click_12"))
{
throw new Exception("btnThreadCore_Click_12异常");
}
else if (name.Equals("btnThreadCore_Click_38"))
{
throw new Exception("btnThreadCore_Click_38异常");
}
Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("")}");
}));
}
//多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;
//那线程异常哪里去了? 被吞了,
//假如我想获取异常信息,还需要通知别的线程
Task.WaitAll(taskList.ToArray());//1 可以捕获到线程的异常
}
catch (AggregateException aex)//2 需要try-catch-AggregateException
{
foreach (var exception in aex.InnerExceptions)
{
Console.WriteLine(exception.Message);
}
}
catch (Exception ex)//可以多catch 先具体再全部
{
Console.WriteLine(ex);
}
//线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要线程取消
//工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作
}
#endregion
多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;线程异常哪里去了? 被吞了
多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息 ,通知别的线程
线程取消
{
CancellationTokenSource cts = new CancellationTokenSource();
var token = cts.Token; cts.Cancel();
CancellationTokenSource cts2 = new CancellationTokenSource();
var token2 = cts2.Token;
List<Task> taskList = new List<Task>();
for (int i = ; i < ; i++)
{
int k = i;
switch (i%)
{
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=0"); }));break;
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=1"); },token)); break;
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=2"); }, token2)); break;
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=3"); })); break;
case :
taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=4");
throw new Exception("throw new Exception");
})); break;
}
}
//Thread.Sleep(500);
cts2.Cancel();
try
{
Task.WaitAll(taskList.ToArray());
}catch(AggregateException ae)
{
foreach (var item in ae.InnerExceptions)
{
Console.WriteLine($"{item.GetType().Name}:{item.Message}");
}
}
Console.WriteLine("**********************************");
foreach (var item in taskList)
{
Console.WriteLine($"Id:{item.Id},Status:{item.Status}");
if (item.Exception != null)
{
foreach (var ex in item.Exception.InnerExceptions)
{
Console.WriteLine($"{ex.GetType().Name}:{ex.Message}");
}
}
}
}
运行上面的代码,有四个任务被取消,取消注释,则有两个任务被取消
线程安全
如果你的代码在进程中有多个线程同时运行这一段,如果每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的
线程安全问题一般都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要多线程都能访问和修改
Lock
1、Lock解决多线程冲突
Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着
推荐锁是private static readonly object
A 不能是Null,可以编译不能运行;
B 不推荐lock(this),外面如果也要用实例,就冲突了
public class LockHelper
{
public void Show()
{
LockTest test = new LockTest();
Console.WriteLine(DateTime.Now);
Task.Delay().ContinueWith(t =>
{
lock (test)
{
Console.WriteLine($"*********Start {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
Thread.Sleep();
Console.WriteLine($"*********End {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
}
});
test.LockThis();
}
}
public class LockTest
{
private int lockthis;
public void LockThis()
{
lock (this)
//递归调用,lock this 会不会死锁? 不会死锁!
//这里是同一个线程,这个引用就是被这个线程所占据
{
Thread.Sleep();
this.lockthis++;
if (this.lockthis < )
this.LockThis();
else
Console.WriteLine($"This is {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
}
}
这里LockThis自身递归调用不会死锁,这个引用被当前线程占用,但当另外的实例要使用时就冲突了,必须等待LockThis执行完成后,释放当前实例,外面的实例才能被调用
C 不应该是string; string在内存分配上是重用的,会冲突
{
LockTest test = new LockTest();
Console.WriteLine(DateTime.Now);
string lockString = "lockString";
Task.Delay().ContinueWith(t =>
{
lock (lockString)
{
Console.WriteLine($"****lockString Start {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
Thread.Sleep();
Console.WriteLine($"****lockString End {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
}
});
test.LockString();
}
public class LockTest
{
private int lockthis;
public void LockThis()
{
lock (this)
//递归调用,lock this 会不会死锁? 不会死锁!
//这里是同一个线程,这个引用就是被这个线程所占据
{
Thread.Sleep();
this.lockthis++;
if (this.lockthis < )
this.LockThis();
else
Console.WriteLine($"This is {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
}
private string lockString= "lockString";
public void LockString()
{
lock (lockString)
{
Thread.Sleep();
Console.WriteLine($"This is {nameof(LockString)}:{this.lockString} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Thread.Sleep();
}
}
}
String类型在内存分配上按享元模式设计的,某个字符串被占用,其他线程就必须等待字符串释放后才能使用
D Lock里面的代码不要太多,这里是单线程的
2、线程安全集合
System.Collections.Concurrent.ConcurrentQueue<T>
3、 数据分拆,避免多线程操作同一个数据;又安全又高效
private int _sync = ;
private int _async = ;
private List<int> listInt = new List<int>();
private static readonly object lockObject = new object();
public void LockObject()
{
for (int i = ; i < ; i++)
{
this._sync++;
}
for (int i = ; i < ; i++)
{
Task.Run(() => this._async++);
}
for (int i = ; i < ; i++)
{
int k = i;
Task.Run(() => this.listInt.Add(k));
}
Thread.Sleep( * );
Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
}
运行上面的代码发现_sync=1000 _async与listInt集合个数都少于1000
public void LockObject()
{
for (int i = ; i < ; i++)
{
this._sync++;
}
for (int i = ; i < ; i++)
{
Task.Run(() => {
lock (lockObject)
{
this._async++;
}
});
}
for (int i = ; i < ; i++)
{
int k = i;
Task.Run(() => {
lock (lockObject)
{
this.listInt.Add(k);
}
});
}
Thread.Sleep( * );
Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
}
使用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
C# 多线程总结 异常处理 线程取消 锁(lock)的更多相关文章
- python线程互斥锁Lock(29)
在前一篇文章 python线程创建和传参 中我们介绍了关于python线程的一些简单函数使用和线程的参数传递,使用多线程可以同时执行多个任务,提高开发效率,但是在实际开发中往往我们会碰到线程同步问题, ...
- Python多线程中阻塞(join)与锁(Lock)的使用误区
参考资料:https://blog.csdn.net/cd_xuyue/article/details/52052893 1使用两个循环分别处理start和join函数.即可实现并发. threads ...
- 线程高级篇-Lock锁和Condition条件
浅谈Synchronized: synchronized是Java的一个关键字,也就是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,执行代码块时,其 ...
- 多线程的异常处理、线程取消、临时变量、lock
异步多线程的异常,抓不到,因为是在子线程执行. #region 多线程的异常处理.线程取消.临时变量.lock { try { List<Task> list = new List< ...
- .NET 异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消
今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...
- .NET异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消
今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...
- -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中
本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait( ...
- python笔记9 线程进程 threading多线程模块 GIL锁 multiprocessing多进程模块 同步锁Lock 队列queue IO模型
线程与进程 进程 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要 ...
- Android多线程研究(9)——线程锁Lock
在前面我们在解决线程同步问题的时候使用了synchronized关键字,今天我们来看看Java 5.0以后提供的线程锁Lock. Lock接口的实现类提供了比使用synchronized关键字更加灵活 ...
随机推荐
- VUE+DRF系列
vue基础系列 001 路飞学诚项目简介 002 Vue简介 003 Vue引入 004 文本指令 005 事件指令 006 斗篷指令 007 属性指令 008 表单指令 009 条件指令 010 路 ...
- Apache用户认证、域名跳转、Apache访问日志
5月29日任务 课程内容: 11.18 Apache用户认证11.19/11.20 域名跳转11.21 Apache访问日志扩展 apache虚拟主机开启php的短标签 http://ask.apel ...
- 带着canvas去流浪系列之七 绘制水球图
[摘要] 用原生canvasAPI实现百度echarts 示例代码托管在:http://www.github.com/dashnowords/blogs 一. 任务说明 使用原生canvasAPI绘制 ...
- 使用modelarts部署bert命名实体识别模型
模型部署介绍 当我们通过深度学习完成模型训练后,有时希望能将模型落地于生产,能开发API接口被终端调用,这就涉及了模型的部署工作.Modelarts支持对tensorflow,mxnet,pytorc ...
- kubernetes学习笔记(二)——部署服务
文章目录 (1)创建app (2)创建app的docker镜像 (3)部署app到k8s (4)参考资料 前面一篇文章部署好了minikube环境,这次学习下怎么部署app到kubernetes环境当 ...
- Spring之跨重定向请求传递数据
摘要 在开发场景中,大部分数据都是使用请求转发(forward)进行传递,而使用重定向(redirect)传递数据可能比较少. 那么问题来了:请求中的数据生命周期存活时间只在一个请求转发(reques ...
- R语言for循环
基本语法:for (name in expr_1) expr_2 实例操作: 1.构造矩阵 x=array(,dim=c(,)) # 构造四阶矩阵 数值全为0 :){ :){ x[i,j]=/(i+j ...
- ceph 网络配置
ceph 网络配置 9. 分离 public network 和 cluster network 9.1 分离的好处 (1)提高性能:消除副本创建.数据恢复和再平衡对 public network 的 ...
- 面试题-关于Java线程池一篇文章就够了
在Java面试中,线程池相关知识,虽不能说是必问提,但出现的频次也是非常高的.同时又鉴于公众号"程序新视界"的读者后台留言让写一篇关于Java线程池的文章,于是就有本篇内容,本篇将 ...
- 【Web技术】334- yarn、npm、cnpm 三者如何优雅的在一起使用 ?
前端得包管理你有过几个? 一位用不好包管理器的前端,是一个入门级前端,一个用不好webpack的前端,是一个初级前端 三个包管理器是可以一起用的,只要你够胆大心细,就没任何问题! 在javeScrip ...