让那些做面试官的屌丝lead不再抖脚系列(四)---线程定义(Thread,Task等),

来源:互联网 发布:ti6双败赛制 知乎 编辑:程序博客网 时间:2024/05/12 17:02

  好了扯了那么多死记硬背的,我们需要点活的。

  在写实际的之前,我们先了解下异步的历史,当然我们在之后会找一个篇幅来引入我们的.net的发展史,这边先了解下异步。


BeginEndInvoke  

其实最原始的异步,大家肯定都用过,所谓的BeginXX/EndXX,详细点就是BeginInvoke()开始调用和EndInvoke阻塞线程,即类似于wait。感觉还是说的太专业了,很简单的例子:

1. BeginInvoke()....

2

3

4

5.EndInvoke()....

6.

执行顺序就是1一边在跑,然后2 3 4也一同在跑,直到跑到5,开始等待begin中的跑完,然后再继续跑6。

当然同步异步带来的就是一个叫callback,即回调函数的东西,就是当异步方法跑完后,还要跑一个方法表示,欧,我结束了,比如我们需要异步获取所有员工的信息,然后在获取完了之后计算总开销,那这个计算方法就是要在获取员工信息的回掉函数中执行,其实很简单的理论,写起来也很简单。

然后是原始.net中begin/endinvoke的写法:


        private  delegate int SuperDelegate(string para);        private static int SuperMethod(string para)        {            return System.Convert.ToInt16(para);        }        private static void SuperCallBack(IAsyncResult result)        {            var returnValue = ((SuperDelegate)result.AsyncState).EndInvoke(result);        }

调用:
            SuperDelegate superDelegate = SuperMethod;            superDelegate.BeginInvoke("1", SuperCallBack, superDelegate);

callback中的returnValue就是我们method的返回值了,说到endInvoke会阻塞线程,其实道理是,谁调用我就塞住谁,所以在callback中调用相当于还是在实行我们method方法的线程中调用,因此主线程不会被阻塞了。

人的想法是无限的,对于代码的美观性更是无限的,所以有了lamda表达式,而对于delegate,用lamda的结果就会变成这样:

           SuperDelegate superDelegate = (s) => System.Convert.ToInt16(s);            superDelegate.BeginInvoke("1", (result) =>             {                var returnValue = superDelegate.EndInvoke(result);            },             superDelegate);

看不懂lamda的童鞋我们之后会特别开一次课让大家明白=>的世界和普通世界是多么不一样。


然后是我们的Task,Thread,这个在现在的版本中反而不多见了,但是这个感觉像是把begin,end封装了的东西,还是有他的好处的。

这两个都是.net4.0才有的产物,先来介绍下task吧,其实也没什么好介绍的。

          var task1 = SyncTask.Task.Factory.StartNew(() =>            {                            });

这里只写了factory模式了,另一个new了再start的就不写了,其实就是很简单的开一个线程,new一个task对象,然后,就根本停不下来的new。

而thread中和task拥有一样的模式,也是一个普通的new的方法,和 ThreadPool.QueueUserWorkItem((callback) => { }); 线程池分配的模式,类似于factory的模式。

那么问题来了,task和thread或者threadpool有神马区别呢。

首先按效率上来说,



速度(最快为1)返回值多参数等待在时限内完成超时后结束ThreadPool.UnsafeQueueUserWorkItem()1非原生支持1非原生支持非原生支持3不支持ThreadPool.QueueUserWorkItem()2.7非原生支持1非原生支持非原生支持3不支持Task()4.5支持2非原生支持支持自愿结束Delegate.BeinInvoke()25.4非原生支持1支持支持4不支持Thread.Start()11009非原生支持1非原生支持非原生支持3支持



这个是从有关部门转载的,可以看到threadpool的方式明显比其他的快,

想要拿到threadpool的参数,我们虽然可以用:

            int aa=0;            ThreadPool.QueueUserWorkItem((callback) => aa = 1 + 2);


这样的,但是其实就是一个代理里再加你想要的方法,而且因为是异步进行,所以无法拿aa做些什么,因为你都不知道他什么时候变成3。

所以先从ThreadPool切入,我们按照强行中断,即取消线程,回调函数,还有扩展,这三方面开始对每个模式进行讨论:


ThreadPool

-> 取消线程:原生是不支持中断的,所以聪明的人类想出了个方法:通过CancellationTokenSource实例,来介入线程干你想干的事,但是负面效果就是,你必须带着他玩:

        private static int CountValue(CancellationTokenSource token , int add1,int add2)        {            if (token.IsCancellationRequested)            {                return 99;            }            return add1 + add2;        }

这是我们自定义的方法,然后调用:

            int aa=0;            CancellationTokenSource cts = new CancellationTokenSource();            ThreadPool.QueueUserWorkItem((callback) => aa = CountValue(cts,1,2));            cts.Cancel();            

所以我们的cts其实就是个监控装置,虽然看起来挺好,但是给方法定义带来了不少麻烦

值得一提的有两点,第一就是线程池木有回调函数的机制可以设置,第二就是,QueueUserWorkItem()的第二个参数,可以设置为前面方法的参数,即类似于Task<>的方式传参,因此这个方法可传参,但是不能接返回值,因此回掉函数也变得意义渐淡。

这里要详细介绍下线程池的概念,首先当然就是ThreadPool和new个Thread的差别,

其实线程池中本来线程就已经安排好,我们可以通过ThreadPool.GetAvailableThreads()获取可用线程数,当然也可以Get/SetMaxThreads()来设置最大线程数,而Thread就是你要一个new一个,结束了就销毁,但是线程池中的线程,完成了任务后就会自动回到池子中,成为挂起的空闲线程待用。因此在性能损耗上,线程池有很大优势。

CLR线程池并不会在CLR初始化时立即建立线程,而是在应用程序要创建线程来运行任务时,线程池才初始化一个线程。

这句话为我们解释了线程池的由来。而对于线程管理,除了我们上面提到的,还有ManualResetEvent这个类,

1、创建一个ManualResetEvent的对象,就像一个信号灯,指示线程的挂起和执行;

2、ManualResetEvent对象创建时,可以指定默认状态:true为有信号,false为无信号;

3、调用Reset()方法重置状态;

4、调用WaitOne()方法,使线程处于等待状态;

5、调用Set()方法设置状态。

这个是转载的,也是很明确,其实threadPool在我眼里,就是个完全靠别人的东西,他自己生活无法自理,需要别的对象来帮助他控制和管理线程。

http://www.cnblogs.com/qingyun163/archive/2013/01/05/2846633.html

这个帖子吧ManualResetEvent的原理描述的很清楚,WaitOne是等待Set,而Reset是重置。


Task<>

这个大家都比较熟悉了,这个比起Thread的优势就是他可以接受返回值

            var task1 = SyncTask.Task.Factory.StartNew<string>(() =>            {                return "aa";            });            var rea = task1.Result;

而task另一个优势就在于他可以通过ContinuedWith()方法持续执行别的方法。

当然,我们依旧可以通过CancellationTokenSource来控制线程:

            CancellationTokenSource cts = new CancellationTokenSource();            var task1 = SyncTask.Task.Factory.StartNew<string>((s) =>            {                return "aa";            }, cts);            cts.Cancel();

同时我们还有这么个神奇的东西:

            CancellationTokenSource cts1 = new CancellationTokenSource();            CancellationTokenSource cts2 = new CancellationTokenSource();            CancellationTokenSource cts3 = new CancellationTokenSource();            CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token, cts3.Token);            var task1 = SyncTask.Task.Factory.StartNew<string>((s) =>            {                return "aa";            }, cts.Token);            ct1.Cancel();


当cts1结束时,task1会被取消,但是cts.Token会被取消,也就是相当于调用了cts.Cancel(),但是,cts2,cts3是不取消的,这点要注意了。


由于篇幅问题,这节我们先到这里,下一节我们将继续Task的深究



0 0
原创粉丝点击