ASP.NETMVC线程和并发

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 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(() => {

相关文档
最新文档