好了扯了那么多死记硬背的,我们需要点活的。
在写实际的之前,我们先了解下异步的历史,当然我们在之后会找一个篇幅来引入我们的.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的深究