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关键字更加灵活 ...
随机推荐
- 计算机通过远程连接登录windows虚拟机具体配置流程
1.打开一台windows虚拟机右击开始,选择——计算机———属性 2.点击允许运行任意版本远程桌面虚拟机连接,点击确定 3.打开服务管理器——用户,如图我一会要用”lisi“用户远程登录此虚拟机 4 ...
- Netty学习——Thrift的入门使用
Netty学习——Thrift的入门使用 希望你能够,了解并使用它.因为它是一个效率很高的框架 官网地址:http://thrift.apache.org/ 1.Thrift数据类型 一门技术如果需要 ...
- 机器学习笔记(九)---- 集成学习(ensemble learning)【华为云技术分享】
集成学习不是一种具体的算法,而是在机器学习中为了提升预测精度而采取的一种或多种策略.其原理是通过构建多个弱监督模型并使用一定策略得到一个更好更全面的强监督模型.集成学习简单的示例图如下: 通过训练得到 ...
- Java修炼——异常的概念以及处理方式(捕获异常)
异常概念分类 异常( Exception 也称例外)就是在程序的运行过程中 所发生的不正常的事件,它会中断正在运行的程序 所需文件找不到 网络连接不通或中断 算术运算错 (被零除-) ...
- 过滤器和监听器实现用户的在线登录人数,以及设置session时长。
过滤器: package com.bjsxt.filter; import java.io.IOException; import javax.servlet.Filter; import javax ...
- linux mysql,tomcat与java的安装
先将服务器的安全组设置为 把所有端口或者所需要的端口开放 然后测试远程访问 ssh 用户@ip 登录 输入密码 service iptables stop ...
- ARTS-S 在docker中运行本地脚本
很多情况是本地没有编译环境,但docker中有编译环境.想编译本地代码,就要把本地的代码和编译脚本映射到docker中,脚本如下: docker run \ -it \ -v /Users/usern ...
- 【Nodejs】392- 基于阿里云的 Node.js 稳定性实践
前言 如果你看过 2018 Node.js 的用户报告,你会发现 Node.js 的使用有了进一步的增长,同时也出现了一些新的趋势. Node.js 的开发者更多的开始使用容器并积极的拥抱 Serve ...
- VMware“该虚拟机似乎正在使用中”
问题现象: 在用VMware虚拟机的时候,有时会发现打开虚拟机时提示"该虚拟机似乎正在使用中.如果该虚拟机未在使用,请按"获取所有权(T)"按钮获取它的所有权.否则,请按 ...
- Vue底层实现原理总结
要实现MVVM 响应式原理,要实现如下几点 1.实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者 2.实现一个指令解析器Compile,对每个元素 ...