ASP.NETMVC线程和并发
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
MVC线程和并发
我也想过跳过C#⾼级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的。我希望通过⾃⼰的经验给⼤家⼀些指引,带着⼤家⼀起⾛上 MVC⼤⽜之路,少⾛弯路。同时也希望能和⼤家⼀起交流,这样也能发现我⾃⼰的不⾜,对我⾃⼰的帮助也是⾮常⼤的。
建议⼤家对C#撑握的不错的时候,可以去看⼀些开源项⽬。⾛技术这条路,就要耐得住寂寞(群⾥双休⽇说要让群主找妹⼦进群的⼈必须反思),练好内功。不撑握C#⾼级知识点,别想看懂优秀的开源项⽬,更别指望吸收其编程思想;你的⽔平,随时可以被⼀个实习⽣代替!切记不能浮躁!
本⽂讲线程和并发,这块知识点太多太多了,不可能⽤⼀篇⽂章写的⾯⾯具到(本⾝主题就是C#⾼级知识概要嘛),我所了解的也有限。但对于Web开发,我想本⽂的知识点应该⾜够,如果后⾯有遇到本⽂没讲的,后⾯再补充吧。
本⽂⽬录:
线程的简单使⽤
常见的并发和异步⼤多是基于线程来实现的,所以本⽂先讲线程的简单使⽤⽅法。
使⽤线程,我们需要引⽤System.Threading命名空间。创建⼀个线程最简单的⽅法就是在 new ⼀个 Thread,并传递⼀个ThreadStart委托(⽆参数)或ParameterizedThreadStart委托(带参数),如下:
class Program {
static void Main(string[] args) {
// 使⽤⽆参数委托ThreadStart
Thread t = new Thread(Go);
t.Start();
// 使⽤带参数委托ParameterizedThreadStart
Thread 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 {
Thread t1 = new Thread(Working);
= "Thread1";
Thread t2 = new Thread(Working);
= "Thread2";
Thread t3 = new Thread(Working);
= "Thread3";
// 依次启动3个线程。
t1.Start();
t2.Start();
t3.Start();
Console.ReadKey();
}
// 每个线程都同时在⼯作
static void Working() {
// 模拟1000次写⽇志操作
for (int i = 0; i < 1000; i++) {
// 异步写⽂件
Logger.Write( + " writes a log: " + i + ", on " + DateTime.Now.ToString() + ".\n");
}// 做⼀些其它的事件
for (int i = 0; i < 1000; i++) { }
}
}
代码很简单,相信⼤家都能看得懂。Logger ⼤家可以把它看做是⼀个写⽇志的组件,先不关⼼它的具体实现,只要知道它是⼀个提供了写⽇志功能的组件就⾏。
那么,这段代码跟并发和异步有什么关系呢?
我们先⽤⼀张图来描述这段代码:
观察上图,3个线程同时调⽤Logger写⽇志,对于Logger来说,3个线程同时交给了它任务,这种情况就是并发。对于其中⼀个线程来说,它在⼯作过程中,在某个时间请求Logger帮它写⽇志,同时⼜继续在⾃⼰的其它⼯作,这种情况就是异步。
(经读者反馈,为不“误导”读者(尽管我个⼈不觉得是误导。之前我的定义和解释不全⾯,没有从操作系统和CPU层次去区分这两个概念。我的⽂章不喜欢搬教科书,只是想⽤通俗易读的⽩话让⼤家理解),为了知识的专业性和严谨,现已把我理解的对并发和异步的定义删除,感谢园友们的热⼼讨论)。
接下来,我们继续讲⼏个很有⽤的有关线程和并发的知识 - 锁、信号机制和线程池。
并发控制 - 锁
CLR 会为每个线程分配⾃⼰的内存堆空间,以使他们的本地变量保持分离互不⼲扰。
线程之间也可以共享通⽤的数据,⽐如同⼀对象的某个属性或全局静态变量。但线程间共享数据是存在安全问题的。举个例⼦,下⾯的主线程和新线程共享了变量done,done⽤来标识某件事已经做过了(告诉其它线程不要再重复做了):
class Program {
static bool done;
new Thread(Go).Start(); // 在新的线程上调⽤Go
Go(); // 在主线程上调⽤Go
Console.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(); // 在新的线程上调⽤Go
Go(); // 在主线程上调⽤Go
Console.ReadKey();
}
static void Go() {
lock (locker) {
if (!done) {
Thread.Sleep(500); // Doing something.
Console.WriteLine("Done");
done = true;
}
}
}
}
再看结果:
使⽤锁,我们解决了问题。但使⽤锁也会有另外⼀个线程安全问题,那就是“死锁”,死锁的概率很⼩,但也要避免。保证“上锁”这个操作在⼀个线程上执⾏是避免死锁的⽅法之⼀,这种⽅法在下⽂案例中会⽤到。
这⾥我们就不去深⼊研究“死锁”了,感兴趣的朋友可以去查询相关资料。
线程的信号机制
有时候你需要⼀个线程在接收到某个信号时,才开始执⾏,否则处于等待状态,这是⼀种基于信号的事件机制。.NET框架提供⼀个ManualResetEvent类来处理这类事件,它的 WaiOne 实例⽅法可使当前线程⼀直处于等待状态,直到接收到某个信号。它的Set⽅法⽤于打开发送信号。下⾯是⼀个信号机制的使⽤⽰例:
static void Main(string[] args) {
var signal = new ManualResetEvent(false);
new Thread(() => {