论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
ToB企服应用市场:ToB评测及商务社交产业平台
»
论坛
›
软件与程序人生
›
后端开发
›
.Net
›
并发编程 - 线程浅试
并发编程 - 线程浅试
三尺非寒
金牌会员
|
2025-1-17 18:28:35
|
来自手机
|
显示全部楼层
|
阅读模式
楼主
主题
893
|
帖子
893
|
积分
2679
前面已经对线程有了开端认识,下面我们来尝试使用线程。
01
、线程创建
在C#中创建线程主要是通过Thread构造函数实现,下面讲授3种常见的创建方式。
1、通过ThreadStart创建
Thread有一个带有ThreadStart类型参数的构造函数,其中参数ThreadStart是一个无参无返回值委托,因此我们可以创建一个无参无返回值方法传入Thread构造函数中,代码如下:
public class ThreadSample
{
public static void CreateThread()
{
Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
var thread = new Thread(BusinessProcess);
thread.Start();
}
//线程1
public static void BusinessProcess()
{
Console.WriteLine($"BusinessProcess 线程Id:{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine("开始处理业务……");
//业务实现
Console.WriteLine("结束处理业务……");
}
}
复制代码
代码也相当简单,我们在主线程中通过Thread创建了一个新的线程用来运行BusinessProcess方法,同时通过Thread.CurrentThread.ManagedThreadId打印出当前线程Id。
代码执行结果如下,主线程Id和业务线程Id并不雷同。
2、通过ParameterizedThreadStart带参创建
Thread另有一个带有ParameterizedThreadStart类型参数的构造函数,其中参数ParameterizedThreadStart是一个有参无返回值委托,其中参数为object类型,因此我们可以创建一个有参无返回值方法传入Thread构造函数中,然后通过Thread.Start方法把参数通报给线程,代码如下:
public static void CreateThreadParameterized()
{
Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
var thread = new Thread(BusinessProcessParameterized);
//传入参数
thread.Start("Hello World!");
}
//带参业务线程
public static void BusinessProcessParameterized(object? param)
{
Console.WriteLine($"BusinessProcess 线程Id:{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"参数 param 为:{param}");
Console.WriteLine("开始处理业务……");
//业务实现
Console.WriteLine("结束处理业务……");
}
复制代码
我们看看代码执行结果:
该方式有个限制,由于ParameterizedThreadStart委托参数为object类型,因此我们的业务方法也必须要用object类型吸取参数,然后再根据实际类型进行转换。
3、通过Lambda表达式创建
通过上面可以知道无论ThreadStart还是ParameterizedThreadStart本质上都是一个委托,因此我们可以直接使用Lambda表达式直接构建一个委托。可以看看以下代码:
public static void CreateThreadLambda()
{
Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
var thread = new Thread(() =>
{
Console.WriteLine($"业务线程Id:{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine("开始处理业务……");
//业务实现
Console.WriteLine("结束处理业务……");
});
//传入参数
thread.Start();
}
复制代码
代码执行结果如下:
由于Lambda表达式可以直接访问外部作用域中的变量,因此线程传参还可以使用Lambda表达式来实现。
但是这也导致了一些问题,比如下面代码执行结果应该是什么?先自己想想看。
public static void CreateThreadLambdaParameterized()
{
Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
var param = "Hello";
var thread1 = new Thread(() => BusinessProcessParameterized(param));
thread1.Start();
param = "World";
var thread2 = new Thread(() => BusinessProcessParameterized(param));
thread2.Start();
}
//带参业务线程
public static void BusinessProcessParameterized(string param)
{
Console.WriteLine($"业务线程Id:{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"参数 param 为:{param}");
}
复制代码
看看执行结果:
和你想想的结果一样吗?
这是由于当在Lambda 表达式中使用任何外部局部变量时,编译器会自动生成一个类,并将该变量作为该类的一个属性。因此这些外部变量并不是存储在栈中,而是通过引用存储在堆中,因此此时param参数实际上在内存中是一个类是一个引用类型,所以两个线程中使用的param都指向了堆中的同一个值。
并且使用Lambda表达式引用另一个C#对象的方式有个专有名词叫闭包。感兴趣的可以去相识下闭包概念。
02
、线程休眠
可以通过Sleep方法暂停当前线程,使其处于休眠状态,以尽可能少的占用CPU时间。看如下示例代码,通过在Sleep方法前后打印出当前时间对比,来观察暂停线程结果。
public static void ThreadSleep()
{
Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
var thread = new Thread(() =>
{
Console.WriteLine($"业务线程Id:{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"暂停线程前:{DateTime.Now:HH:mm:ss}");
//暂停线程10秒
Thread.Sleep(10000);
Console.WriteLine($"暂停线程后:{DateTime.Now:HH:mm:ss}");
});
thread.Start();
thread.Join();
}
复制代码
代码执行结果如下:
可以发现暂停线程前后恰恰差了10秒钟。
03
、线程等待
线程等待指让程序等待另一个必要长时间计算的线程运行完成后,再继续后面操纵。而使用Thread.Sleep方法并不能满足需求,由于当前并不知道执行计算到底必要多少时间,因此可以使用Thread.Join。如上一小节中代码,当代码执行到Thread.Join方法时,则线程会处于阻塞状态,只有线程执行完成后才会继续往下执行。具体示例可以看上一小节。
04
、线程其他方法
此外线程另有暂停、恢复、中断、停止等线程方法,这里就不先容了,由于一些方法已经弃用没有须要再花经历学习了。
05
、异常处理
对于线程中的异常必要特别注意,对于一个Thread子线程所产生的异常,默认环境下主线程并不能捕获到,可以查看下面示例:
public static void ThreadException()
{
Console.WriteLine($"主线程Id:{Thread.CurrentThread.ManagedThreadId}");
try
{
var thread = new Thread(ThreadThrowException);
thread.Start();
}
catch (Exception ex)
{
Console.WriteLine("子线程异常信息:" + ex.Message);
}
}
//业务线程不处理异常,直接抛出
public static void ThreadThrowException()
{
Console.WriteLine($"业务线程Id:{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine("开始处理业务……");
//业务实现
Console.WriteLine("结束处理业务……");
throw new Exception("异常");
}
复制代码
运行结果如下:
可以看到在主线程中并没有捕获到子线程抛出的异常,而导致程序直接中断。因此我们在处理线程异常时必要特别注意,可以直接在线程中处理异常。
06
、何时应该使用线程
线程有许多优点,但也并不是万能的,由于每一个线程都会产生大量的资源斲丧,包罗:占用大量内存空间,线程的创建、销毁和管理,线程之间的上下文切换,以及垃圾回收的斲丧。
举个简单例子,比如一个小餐馆,有一个厨师,一个下单员,客户下单给下单员,下单员把客户下的菜单通报给厨师。假如现在客户许多一个下单员忙不过来,老板决定再添加一个下单员,此时下单的效率可以提升一倍,但是厨师还是一个,那么就会导致当厨师和A下单员交接的时间,B下单员只能等着,并且由于之前厨师和A下单员长时间合作形成了相互默契,这是再和B下单员交接的时间效率可能并不高,因此终极团体效率并不愿定提升多少。如果把厨师比作CPU处理器,下单员比作线程,如果要想餐馆的团体效率提升那么在增加下单员的时间,必须要相应的添加厨师,才能使得餐馆最大效率的提升。
因此并不是说无脑的添加线程就可以使得程序效率提升,必要按需使用。
比如在以下使用场景可以考虑使用多线程:文件多写、网络请求、数据库查询、图像处理、数据分析、定时任务等。
注
:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。
https://gitee.com/hugogoos/Planner
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
倒序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
回帖后跳转到最后一页
发新帖
回复
三尺非寒
金牌会员
这个人很懒什么都没写!
楼主热帖
可观测性之两大误区
微信小程序--点餐系统(本地服务器+源 ...
Centos7安装Mysql5.7(超详细版) ...
Java多线程超级详解(只看这篇就够了) ...
GPRS与4G网络:技术差异与应用选择 ...
小白也可以轻松破解被加密的ZIP口令啦 ...
“远程客户端操作hdfs创建文件夹”,验 ...
如何从命令行启动 CST 软件? ...
环形缓冲区 Ring Buffer 的实现 ...
Synchronized,我要一层一层剥开你的心 ...
标签云
挺好的
服务器
快速回复
返回顶部
返回列表