C#线程同步的三类情景分析
作者:shichen2014 发布时间:2023-01-07 22:38:04
本文实例讲述了C#线程同步的三类情景,分享给大家供大家参考。具体分析如下:
C# 已经提供了我们几种非常好用的类库如 BackgroundWorker、Thread、Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序。
比如这样一个需求:有一个 Winform 窗体,点击按钮后,会将窗体中的数据导出到一个 output.pdf 文件中。原先的代码没有采用多线程技术,所以当点击按钮后,整个窗体就变成无响应了。为了解决这个问题,可以使用 Task.Run(()=>{...导出文件的代码});
上面的代码看似简单,却隐藏着种种危机。如果在导出的期间,窗体的数据被修改了,那会怎么样?如果多个窗体同时导出到同一个文件,又会怎么样?
在看完本系列后,你就会清楚了。
有点了解的朋友都知道线程同步有多种手段,什么 mutex、moniter、seamphore、event 等等,我把它们归为三类,对应三种需要线程同步的情景。
情景一:此茅坑有主了
当一个资源同时被多个线程访问时,有可能会造成资源冲突(尤其是在存在多个写线程的时候)的情景。遇到这种情况,在 C# 中,我们可以使用 Interlocked、lock、Moniter、SpinLock、ReadWriteLockSlim、Mutex 来处理问题。
什么情况下会被认为是情景一?
当你设计的类中出现静态变量、IO操作时,就会遇到情景一。因为这些资源是由多个对象共享的,不同的线程很同时去访问这些资源时,就可能会出现争用。
当一个类被设计成单例,且包含实例变量时,也会遇到情景一。因为实例变量属于这个单例,当多个线程操纵此单例时,该变量可能会被争用。
当一个类中的方法调用线程操作某个实例变量时,也会遇到情景一。
情景二:数量有限,先到先得
情景一强调的是一对多的情形,而在情景二中,资源的数量并不唯一。相比于情景一,情景二侧重的是数量上的限制。而用于实现这一需求的类有:Semaphore、SemaphoreSlim。
什么情况下会被认为是情景二?
当所操作的公共资源存在并发数限制的时候(如数据库连接、IIS连接数限制等),就被认为是情景二。
情景三:我让你动,你才能动!
情景三关注的是线程执行过程中的先后顺序,而用于保证这种先后顺序的方式就是通过线程通信的方式:ManualResetEventSlim、ManualResetEvent、AutoResetEvent。
什么情况下会被认为是情景三?
当两个线程所处理的事情有先后的依赖时,比如线程二的执行过程依赖线程一的执行结果,那就认为是情景三。
不限使用情景
上面的各种方案并不是绝对只限于某一场景,比如 AutoResetEvent 即可以用于情景三,也可以用于情景一。但是,杀鸡焉用牛刀,虽然使用 AutoResetEvent 能够实现情景一的需求,但是用不了 AutoResetEvent 的线程通信能力,同时又会有一些额外的限制(每个线程必须保证 wait 和 set 的成对使用,否则一个线程在锁定资源后就可能被另一个线程解锁)。
lock (m)
{
//....
}
//等价于如下方式
autoResetEvent.WaitOne();
//....
autoResetEvent.Set();
也有朋友说,可以用情景一中的 lock 方案来实现情景三的需求。
AutoResetEvent autoReset = new AutoResetEvent(false);
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
autoReset.WaitOne();
Console.WriteLine("步骤二");
});
Thread.Sleep(1000);//故意延迟从而保证第二个线程是在第一个线程之后才执行
Task.Run(() =>
{
Console.WriteLine("步骤一");
autoReset.Set();
});
}
上面这个例子最终输出的结果可想而知。此实例说明,不管线程实际的执行顺序如何,AutoResetEvent 都能很容易的保证两个线程的执行顺序。
如果用 lock 呢?
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
lock (s)
{
Console.WriteLine("步骤一");
}
});
Thread.Sleep(1000);//必须人为确保步骤二的线程要在步骤一的线程之后执行
Task.Run(() =>
{
lock (s)
{
Console.WriteLine("步骤二");
}
});
}
虽然能实现,但是需要花费额外的代码去人为保证两个线程的执行顺序。
如何在这么多方案中确定最终所使用的,需要你能对项目的各种情景进行分析,根据实际情景选择对应的方案,而不至于大材小用。
总 结
通过本系列文章的介绍,相信能让大家能对多线程中可能碰到的情景有一个概念,不至于在面临多线程的时候手忙脚乱。
希望本文所述对大家的C#程序设计有所帮助。


猜你喜欢
- 使用GroupingSearch对搜索结果进行分组Package org.apache.lucene.search.grouping Des
- 构造http headerprivate static final String URL = "url";private
- 这篇文章主要介绍了Spring配置文件如何使用${username},文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习
- 本文实例为大家分享了Java实现斗地主游戏的具体代码,供大家参考,具体内容如下原理图:斗地主过程: 1、组合牌&nb
- 本文实例讲述了Android实时文件夹创建方法。分享给大家供大家参考。具体如下:实时文件夹是一种用来显示由某个ContentProvider
- Windows操作系统可以实现重命名文件操作,却不能实现批量重命名。本实例实现了批量重命名功能,可以将一个文件夹内同一类型的文件按照一定的规
- 目录问题:解答方案:总结问题:我遇到了一个有趣的问题,它的代码大概是这样的。List<Func<int>> acti
- 题意Description相信大家都做过"A+B Problem"了吧,这道题是它的加强版。输入两个整数 A , B ,
- 这篇文章主要介绍了Java模拟多线程实现抢票,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考
- 桶排序桶排序是计数排序的升级,计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素,通过函数的某种映射关系,将待排序数组中
- 上一篇介绍了Tesseract库的使用(OCR库Tesseract初探),文末提到了Tesseract是用c/c++开发的,也有C#的开源版
- 一、时区的基本概念GMT(Greenwich Mean Time),即格林威治标准时,是东西经零度的地方。人们将地球人为的分为24等份,每一
- 本文实例为大家分享了Android简单使用PopupWindow的的具体代码,供大家参考,具体内容如下思路1.在res下面创建一个menu文
- 刚接触maven就是在公司里配置好的,所以一直以来使用都没毛病,所以一直没有去动这些固有的东西。但是,后来把公司的电脑拿回家之后,发现有的东
- 本文实例讲述了Android开发之SeekBar基本使用及各种美观样式。分享给大家供大家参考,具体如下:改变控件透明度只需通过 .setAl
- Java微信跳一跳操作指南,指哪挑哪。本文的思路是通过adb来控制手机进行操作,通过java写一个jframe覆盖在手机屏幕上,用鼠标获取跳
- Java中的set是无序的,但是是不可重复的HashSet底层是哈希表,通过调用hashcode和equals方法实现去重当我们HashSe
- 最近项目里面做了一个定时器,结果报错这个。网上的原因大多说是什么版本问题。我记录下我的问题所在。由于项目启动在局域网,不能访问互联网。打出来
- 前言悬浮窗是一种比较常见的需求。例如把视频通话界面缩小成一个悬浮窗,然后用户可以在其他界面上处理事情。本文给出一个简单的应用内悬浮窗实现。可
- (一)单线程递归方式package com.taobao.test;import java.io.File;public class Tot