ASP.NET MVC 线程和并发
我也想过跳过C#高级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的。我希望通过自己的经验给大家一些指引,带着大家一起走上ASP.NET MVC大牛之路,少走弯路。同时也希望能和大家一起交流,这样也能发现我自己的不足,对我自己的帮助也是非常大的。
建议大家对C#撑握的不错的时候,可以去看一些开源项目。走技术这条路,就要耐得住寂寞(群里双休日说要让群主找妹子进群的人必须反思),练好内功。不撑握C#高级知识点,别想看懂优秀的开源项目,更别指望吸收其编程思想;你的水平,随时可以被一个实习生代替!切记不能浮躁!
本文讲线程和并发,这块知识点太多太多了,不可能用一篇文章写的面面具到(本身主题就是C#高级知识概要嘛),我所了解的也有限。但对于Web开发,我想本文的知识点应该足够,如果后面有遇到本文没讲的,后面再补充吧。
本文目录:
- 线程的简单使用
- 并发和异步的区别
- 并发控制 - 锁
- 线程的信号机制
- 线程池中的线程
- 案例:支持并发的异步日志组件
- 结语
线程的简单使用
常见的并发和异步大多是基于线程来实现的,所以本文先讲线程的简单使用方法。
class Program {static void Main(string[] args) {// 使用无参数委托ThreadStartThread t = new Thread(Go);t.Start();// 使用带参数委托ParameterizedThreadStartThread t2 = new Thread(GoWithParam);t2.Start("Message from main.");t2.Join();// 等待线程t2完成。Console.WriteLine("Thread t2 has ended!");Console.ReadKey();}static void Go() {Console.WriteLine("Go!");}static void GoWithParam(object msg) {Console.WriteLine("Go With Param! Message: " + msg);Thread.Sleep(1000);// 模拟耗时操作}
}
运行结果:
线程的用法,我们只需要了解这么多。下面我们再来通过一段代码来讲讲并发和异步。
并发和异步的区别
关于并发和异步,我们先来写一段代码,模拟多个线程同时写1000条日志:
class Program {static void Main(string[] args) {Thread t1 = new Thread(Working);t1.Name = "Thread1";Thread t2 = new Thread(Working);t2.Name = "Thread2";Thread t3 = new Thread(Working);t3.Name = "Thread3";// 依次启动3个线程。t1.Start();t2.Start();t3.Start();Console.ReadKey();}// 每个线程都同时在工作static void Working() {// 模拟1000次写日志操作for (int i = 0; i < 1000; i++) {// 异步写文件Logger.Write(Thread.CurrentThread.Name + " writes a log: " + i + ", on " + DateTime.Now.ToString() + ".\n");}// 做一些其它的事件for (int i = 0; i < 1000; i++) { }}
}
代码很简单,相信大家都能看得懂。Logger 大家可以把它看做是一个写日志的组件,先不关心它的具体实现,只要知道它是一个提供了写日志功能的组件就行。
那么,这段代码跟并发和异步有什么关系呢?
我们先用一张图来描述这段代码:
观察上图,3个线程同时调用Logger写日志,对于Logger来说,3个线程同时交给了它任务,这种情况就是并发。对于其中一个线程来说,它在工作过程中,在某个时间请求Logger帮它写日志,同时又继续在自己的其它工作,这种情况就是异步。
(经读者反馈,为不“误导”读者(尽管我个人不觉得是误导。之前我的定义和解释不全面,没有从操作系统和CPU层次去区分这两个概念。我的文章不喜欢搬教科书,只是想用通俗易读的白话让大家理解),为了知识的专业性和严谨,现已把我理解的对并发和异步的定义删除,感谢园友们的热心讨论)。
接下来,我们继续讲几个很有用的有关线程和并发的知识 - 锁、信号机制和线程池。
并发控制 - 锁
CLR 会为每个线程分配自己的内存堆空间,以使他们的本地变量保持分离互不干扰。
class Program {static bool done;static void Main(string[] args) {new Thread(Go).Start(); // 在新的线程上调用GoGo(); // 在主线程上调用GoConsole.ReadKey();}static void Go() {if (!done) {Thread.Sleep(500); // 模拟耗时操作Console.WriteLine("Done"); done = true;}}
}
输出结果:
输出了两个“Done”,事件被做了两次。由于没有控制好并发,这就出现了线程的安全问题,无法保证数据的状态。
要解决这个问题,就需要用到锁(Lock,也叫排它锁或互斥锁)。使用lock语句,可以保证共享数据只能同时被一个线程访问。lock的数据对象要求是不能null的引用类型的对象,所以lock的对象需保证不能为空。为此需要创建一个不为空的对象来使用锁,修改一下上面的代码如下:
class Program {static bool done;static object locker = new object(); // !!static void Main(string[] args) {new Thread(Go).Start(); // 在新的线程上调用GoGo(); // 在主线程上调用GoConsole.ReadKey();}static void Go() {lock (locker) {if (!done) {Thread.Sleep(500); // Doing something.Console.WriteLine("Done");done = true;}}}
}
再看结果:
使用锁,我们解决了问题。但使用锁也会有另外一个线程安全问题,那就是“死锁”,死锁的概率很小,但也要避免。保证“上锁”这个操作在一个线程上执行是避免死锁的方法之一,这种方法在下文案例中会用到。
这里我们就不去深入研究“死锁”了,感兴趣的朋友可以去查询相关资料。
线程的信号机制
static void Main(string[] args) {var signal = new ManualResetEvent(false);new Thread(() => {Console.WriteLine("Waiting for signal...");signal.WaitOne();signal.Dispose();Console.WriteLine("Got signal!");}).Start();Thread.Sleep(2000);signal.Set();// 打开“信号”Console.ReadKey();
}
运行结果:
当执行Set方法后,信号保持打开状态,可通过Reset方法将其关闭,若不再需要,通过Dispose将其释放。如果预期的等待时间很短,可以用ManualResetEventSlim代替ManualResetEvent,前者在等待时间较短时性能更好。信号机制非常有用,后面的日志案例会用到它。
线程池中的线程
线程池中的线程是由CLR来管理的。在下面两种条件下,线程池能起到最好的效用:
// 方式1:Task.Run,.NET Framework 4.5 才有
Task.Run (() => Console.WriteLine ("Hello from the thread pool"));// 方式2:ThreadPool.QueueUserWorkItem
ThreadPool.QueueUserWorkItem (t => Console.WriteLine ("Hello from the thread pool"));
线程池使得线程可以充分有效地被使用,减少了任务启动的延迟。但是不是所有的情况都适合使用线程池中的线程,比如下面要讲的日志案例 - 异步写文件。
这里讲线程池,是为了让大家大致了解什么时候用线程池中的线程,什么时候不用。即,耗时长或有阻塞情况的不用线程池中的线程。
创建不走线程池中的线程,可以直接通过new Thread来创建,也可以通过下面的代码来创建:
Task task = Task.Factory.StartNew (() => ...,TaskCreationOptions.LongRunning);// 注意必须带TaskCreationOptions.LongRunning参数
这里用到了Task,大家不用关心它,后续博文会详细讲。
关于线程的知识很多,这里不再深入了,因为这些已经足够让我们应付Web开发了。
案例:支持并发的异步日志组件
上文的“并发和异步的区别”的代码中我们用到了一个Logger类,现在我们就来做一个这样的Logger。
基于上面的知识,我们可以实现应用程序的并发写日志日志功能。在应用程序中,写日志是常见的功能,简单分析一下该功能的需求:
- 在后台异步执行,和其它线程互不影响。
根据上文线程池的两个最优使用条件,由写日志线程会长时间处于阻塞(或运行等待)状态,所以它不适合使用线程池。即不能使用Task.Run,而最好使用new Thread。 - 支持并发,即多个任务(分布在不同线程上)可同时调用写日志功能,但需保证线程安全。
支持并发,必然要用到锁,但要完全保证线程安全,那就要想办法避免“死锁”。只要我们把“上锁”的操作始终由同一个线程来做即可避免“死锁”问题,但这样的话,并发请求的任务只能放在队列中由该线程依次执行(因为是后台执行,无需即时响应用户,所以可以这么做)。 - 单个实例,单个线程。
任何地方调用写日志功能都调用的是同一个Logger实例(显然不能每次写日志都新建一个实例),即需使用单例模式。不管有多少任务调用写日志功能,都必须始终使用同一个线程来处理这些写日志操作,以保证不占用过多的线程资源和避免新建线程带来的延迟。
- 需要一个用来存放写日志任务的队列。
- 需要有一个信号机制来标识是否有新的任务要执行。
- 当有新的写日志任务时,将该任务加入到队列中,并发出信号。
- 用一个方法来处理队列中的任务,当接收新任务信号时,就依次调用队列中的任务。
开发一个功能前需要有个简单的思路,保证心里面有底。具体开发的时候会发现问题,然后再去补充扩展和完善等。刚开始很难想得太周全,先有个简单的思路,然后代码写起来!
public class Logger {// 用于存放写日志任务的队列private Queue<Action> _queue;// 用于写日志的线程private Thread _loggingThread;// 用于通知是否有新日志要写的“信号器”private ManualResetEvent _hasNew;// 构造函数,初始化。private Logger() {_queue = new Queue<Action>();_hasNew = new ManualResetEvent(false);_loggingThread = new Thread(Process);_loggingThread.IsBackground = true;_loggingThread.Start();}// 使用单例模式,保持一个Logger对象private static readonly Logger _logger = new Logger();private static Logger GetInstance() {/* 不安全代码lock (locker) {if (_logger == null) {_logger = new Logger();}}*/return _logger;}// 处理队列中的任务private void Process() {while (true) {// 等待接收信号,阻塞线程。_hasNew.WaitOne();// 接收到信号后,重置“信号器”,信号关闭。_hasNew.Reset(); // 由于队列中的任务可能在极速地增加,这里等待是为了一次能处理更多的任务,减少对队列的频繁“进出”操作。Thread.Sleep(100);// 开始执行队列中的任务。// 由于执行过程中还可能会有新的任务,所以不能直接对原来的 _queue 进行操作,// 先将_queue中的任务复制一份后将其清空,然后对这份拷贝进行操作。Queue<Action> queueCopy;lock (_queue) {queueCopy = new Queue<Action>(_queue);_queue.Clear();}foreach (var action in queueCopy) {action();}}}private void WriteLog(string content) {lock (_queue) { // todo: 这里存在线程安全问题,可能会发生阻塞。// 将任务加到队列_queue.Enqueue(() => File.AppendAllText("log.txt", content));}// 打开“信号”_hasNew.Set();}// 公开一个Write方法供外部调用public static void Write(string content) {// WriteLog 方法只是向队列中添加任务,执行时间极短,所以使用Task.Run。Task.Run(() => GetInstance().WriteLog(content));}
}
类写好了,用上文“并发和异步的区别”中的代码测试一下这个Logger类,在我的电脑上运行的一次结果:
共3000条日志,结果没有问题。
上面的Logger类注释写得很详细,我就不再解析了。
通过这个示例,目的是让大家掌握线程和并发在开发中的基本应用和要注意的问题。
遗憾的是这个Logger类并不完美,而且存在线程安全问题(代码中用红色字体标出),虽然实际环境概率很小。可能上面代码多次运行都很难看到有异常发生(我多次运行未发生异常),但同时再添加几个线程可能就会有问题了。
那么,如何解决这个线程安全问题呢?
转:https://blog.csdn.net/albert528108/article/details/52416809
ASP.NET MVC 线程和并发相关推荐
- [渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序处理并发
这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第十篇:为ASP.NET MVC应用程序 ...
- ASP.NET MVC下的异步Action的定义和执行原理
Visual Studio提供的Controller创建向导默认为我们创建一个继承自抽象类Controller的Controller类型,这样的Controller只能定义同步Action方法.如果我 ...
- ASP.NET MVC导出excel(数据量大,非常耗时的,异步导出)
要在ASP.NET MVC站点上做excel导出功能,但是要导出的excel文件比较大,有几十M,所以导出比较费时,为了不影响对界面的其它操作,我就采用异步的方式,后台开辟一个线程将excel导出到指 ...
- WinDBg定位asp.net mvc项目异常崩溃源码位置
项目介绍:asp.net mvc + angular +iis+windows server 系统莫名崩溃 最近有个系统默认奇妙崩溃50x,服务整体变成无响应,当运维告知我只有重启应用程序池项目才能正 ...
- Asp.net MVC 教程汇总
自学MVC看这里--全网最全ASP.NET MVC 教程汇总 MVC架构已深得人心,微软也不甘落后,推出了Asp.net MVC.小编特意整理博客园乃至整个网络最具价值的MVC技术原创文章,为想 ...
- ASP.NET MVC 教程学习
1. Why :为什么需要ASP.NET MVC 本章主要为大家汇总了为什么学习Asp.net MVC替代WebForms,产生ASP.NET MVC 的需求是什么,只有更好的理解了为什么需要MVC, ...
- 简述C#中IO的应用 RabbitMQ安装笔记 一次线上问题引发的对于C#中相等判断的思考 ef和mysql使用(一) ASP.NET/MVC/Core的HTTP请求流程...
简述C#中IO的应用 在.NET Framework 中. System.IO 命名空间主要包含基于文件(和基于内存)的输入输出(I/O)服务的相关基础类库.和其他命名空间一样. System.IO ...
- Pro ASP.NET MVC 3 Framework 译文(一)
ASP.NET MVC3简介 2011年10月22日 12:49 对于使用微软平台的开发人员来说,ASP.NET MVC框架有了一个根本的转变.它强调"干净的"体系.设计模式.可测 ...
- ASP.NET MVC 3 Framework之旅 第一章
第一章 伟大的思想 ASP.NET MVC是来自于微软的整合了有效性的和整洁的模式-视图-控制器(MVC)架构的一种Web开发框架,它的最新的思想和技术来自于敏捷性开发,是最好的ASP.NET平台.A ...
最新文章
- 工资倒挂也刺激不了已是咸鱼的你
- mysql主从 percona_mysql主从实现(percona-xtrabackup)
- 综合布线管理系统之智能配线架的过去与将来
- java arraylist 常用方法_分享ArrayList中的几个常用方法的源码
- 正确的LeetCode刷题姿势!
- 北京冬奥会科技感十足多家科技公司助力
- LeetCode For SQL 184. 部门工资最高的员工 (分组 from嵌套)
- Illustrator 教程,如何在 Illustrator 中重新塑造文本?
- 理解密码学中的双线性映射
- 【Python】第5次练习:def 定义函数——编写函数求和、质数判断、lambda函数计算三次幂
- 我的2016---悲喜交加的一年
- java中私有变量和方法_Java 私有变量和私有方法
- windows mysql提示:1045 access denied for user 'root'@'localhost' using password ye
- 微信小程序开发笔记(二)
- 关于研发规范化的一些实践和思考
- 段码式显示屏和背光电路怎么设计?
- 倒计时2天:百度“文心一言”即将上线!
- abb机器人控制箱按键的作用_ABB机器人控制柜各个部件及作用介绍
- CXF报错及解决方案
- 哈夫曼树构建与哈夫曼树编码
热门文章
- C++Wiggle Sort摆动排序的实现算法(附完整源码)
- C++ deque方法
- c++备忘录模式mememto
- 经典C语言程序100例之十四
- c语言程序设计第四版十二五,C语言程序设计/普通高等教育十二五规划教材
- ack机制之代码实现,实现BaseRichBolt的方式,使用BaseBasicBolt的方式实现BaseRichBolt发ack和fail的功能
- Redis和nosql简介,api调用;Redis数据功能(String类型的数据处理);List数据结构(及Java调用处理);Hash数据结构;Set数据结构功能;sortedSet(有序集合)数
- Spring MVC中jsessionid所引起的问题 和解决
- 1.cocos2dx记忆卡片游戏代码、并将游戏移植到“华为荣耀”手机上、移植中的问题总结
- 通过MULE集成服务的几种方式