线程
对于所有需要等待的操作,例如移动文件,数据库和网络访问都需要一定的时间,此时就可以启动一个新的线程,同时完成其他任务。一个进程的多个线程可以同时运行在不同的CPU上或多核CPU的不同内核上。
异步委托
创建线程的一种简单方式是定义一个委托,并异步调用它。 委托是方法的类型安全的引用。Delegate类 还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。
static int TakesAWhile(int data,int ms){
Console.WriteLine("TakesAWhile started!");
Thread.Sleep(ms);//程序运行到这里的时候会暂停ms毫秒,然后继续运行下一语句
Console.WriteLine("TakesAWhile completed");
return ++data;
}
public delegate int TakesAWhileDelegate(int data,int ms);// 声明委托
static void Main(){
TakesAWhileDelegate d1 = TakesAWhile;
IAsyncResult ar = d1.BeginInvoke(1,3000,null,null);
while(ar.IsCompleted ==false ){
Console.Write(".");
Thread.Sleep(50);
}
int result = d1.EndInvoke(ar);
Console.WriteLine("Res:"+result);
}
等待句柄IAsyncResult.AsyncWaitHanlde
当我们通过BeginInvoke开启一个异步委托的时候,返回的结果是IAsyncResult,我们可以通过它的AsyncWaitHandle属性访问等待句柄。这个属性返回一个WaitHandler类型的对象,它中的WaitOne()方法可以等待委托线程完成其任务,WaitOne方法可以设置一个超时时间作为参数(要等待的最长时间),如果发生超时就返回false。
static void Main(){
TakesAWhileDelegate d1 = TakesAWhile;
IAsyncResult ar = d1.BeginInvoke(1,3000,null,null);
while(true){
Console.Write(".");
if(ar.AsyncWaitHanle.WaitOne(50,false)){
Console.WriteLine("Can get result now");
break;
}
}
int result = d1.EndInvoke(ar);
Console.WriteLine("Res:"+result);
}
异步回调-回调方法
等待委托的结果的第3种方式是使用异步回调。在BeginInvoke的第三个参数中,可以传递一个满足AsyncCallback委托的方法,AsyncCallback委托定义了一个IAsyncResult类型的参数其返回类型是void。对于最后一个参数,可以传递任意对象,以便从回调方法中访问它。(我们可以设置为委托实例,这样就可以在回调方法中获取委托方法的结果)
static void Main(){
TakesAWhileDelegate d1 = TakesAWhile;
d1.BeginInvoke(1,3000,TakesAWhileCompleted,d1);
while(true){
Console.Write(".");
Thread.Sleep(50);
}
}
static void TakesAWhileCompleted(IAsyncResult ar){//回调方法是从委托线程中调用的,并不是从主线程调用的,可以认为是委托线程最后要执行的程序
if(ar==null) throw new ArgumentNullException("ar");
TakesAWhileDelegate d1 = ar.AsyncState as TakesAWhileDelegate;
int result = d1.EndInvoke(ar);
Console.Write("Res:"+result);
}
异步回调-Lambda表达式
等待委托的结果的第3种方式是使用异步回调。在BeginInvoke的第三个参数中,可以传递一个满足AsyncCallback委托的方法,AsyncCallback委托定义了一个IAsyncResult类型的参数其返回类型是void。对于最后一个参数,可以传递任意对象,以便从回调方法中访问它。(我们可以设置为委托实例,这样就可以在回调方法中获取委托方法的结果)
static void Main(){
TakesAWhileDelegate d1 = TakesAWhile;
d1.BeginInvoke(1,3000,ar=>{
int result = d1.EndInvoke(ar);
Console.WriteLine("Res:"+result);
},null);
while(true){
Console.Write(".");
Thread.Sleep(50);
}
}
Thread类
static void Main(){
var t1 = new Thread(ThreadMain);
t1.Start();
Console.WriteLine("This is the main thread.");
}
static void ThreadMain(){
Console.WriteLine("Running in a thread.");
}
Thread类-Lambda表达式
static void Main(){
var t1 = new Thread( ()=>Console.WriteLine("Running in a thread, id : "+Thread.CurrentThread.ManagedThreadId) );
t1.Start();
Console.WriteLine("This is the main Thread . ID : "+Thread.CurrentThread.ManagedThreadId)
}
给线程传递数据-通过委托
给线程传递一些数据可以采用两种方式,一种方式是使用带ParameterizedThreadStart委托参数的Thread构造函数,一种方式是创建一个自定义的类,把线程的方法定义为实例方法,这样就可以初始化实例的数据,之后启动线程。
public struct Data{//声明一个结构体用来传递数据
public string Message;
}
static void ThreadMainWithParameters(Object o ){
Data d=(Data)o;
Console.WriteLine("Running in a thread , received :"+d.Message);
}
static void Main(){
var d = new Data{Message = "Info"};
var t2 = new Thread(ThreadMainWithParameters);
t2.Start(d);
}
给线程传递数据-自定义类
public class MyThread{
private string data;
public MyThread(string data){
this.data = data;
}
public void ThreadMain(){
Console.WriteLine("Running in a thread , data : "+data);
}
}
var obj = new MyThread("info");
var t3 = new Thread(obj.ThreadMain);
t3.Start();
后台线程和前台线程
只有一个前台线程在运行,应用程序的进程就在运行,如果多个前台线程在运行,但是Main方法结束了,应用程序的进程仍然是运行的,直到所有的前台线程完成其任务为止。
在默认情况下,用Thread类创建的线程是前台线程。线程池中的线程总是后台线程。
在用Thread类创建线程的时候,可以设置IsBackground属性,表示它是一个前台线程还是一个后台线程。
class Program{
static void Main(){
var t1 = new Thread(ThreadMain){IsBackground=false};
t1.Start();
Console.WriteLine("Main thread ending now.");
}
static void ThreadMain(){
Console.WriteLine("Thread +"+Thread.CurrentThread.Name+" started");
Thread.Sleep(3000);
Console.WriteLine("Thread +"+Thread.CurrentThread.Name+" started");
}
}
后台线程用的地方:
如果关闭Word应用程序,拼写检查器继续运行就没有意义了,在关闭应用程序的时候,拼写检查线程就可以关闭。
当所有的前台线程运行完毕,如果还有后台线程运行的话,所有的后台线程会被终止掉。
线程的优先级
线程有操作系统调度,一个CPU同一时间只能做一件事情(运行一个线程中的计算任务),当有很多线程需要CPU去执行的时候,线程调度器会根据线程的优先级去判断先去执行哪一个线程,如果优先级相同的话,就使用一个循环调度规则,逐个执行每个线程。
在Thead类中,可以设置Priority属性,以影响线程的基本优先级 ,Priority属性是一个ThreadPriority枚举定义的一个值。定义的级别有Highest ,AboveNormal,BelowNormal 和 Lowest。
任务
在.NET4 新的命名空间System.Threading.Tasks包含了类抽象出了线程功能,在后台使用的ThreadPool进行管理的。任务表示应完成某个单元的工作。这个工作可以在单独的线程中运行,也可以以同步方式启动一个任务。
任务也是异步编程中的一种实现方式。
启动任务
启动任务的三种方式:
TaskFactory tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod);
Task t2 = TaskFactory.StartNew(TaskMethod);
Task t3 = new Task(TaskMethod);
t3.Start();
我们创建任务的时候有一个枚举类型的选项TaskCreationOptions
连续任务
如果一个任务t1的执行是依赖于另一个任务t2的,那么就需要在这个任务t2执行完毕后才开始执行t1
static void DoFirst(){
Console.WriteLine("do in task : "+Task.CurrentId);
Thread.Sleep(3000);
}
static void DoSecond(Task t){
Console.WriteLine("task "+t.Id+" finished.");
Console.WriteLine("this task id is "+Task.CurrentId);
Thread.Sleep(3000);
}
Task t1 = new Task(DoFirst);
Task t2 = t1.ContinueWith(DoSecond);
Task t3 = t1.ContinueWith(DoSecond);
Task t4 = t2.ContinueWith(DoSecond);
Task t5 = t1.ContinueWith(DoError,TaskContinuationOptions.OnlyOnFaulted);
任务层次结构
我们在一个任务中启动一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行,如果父任务执行完了但是子任务没有执行完,它的状态会设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态就变成RunToCompletion
static void Main(){
var parent = new Task(ParentTask);
parent.Start();
Thread.Sleep(2000);
Console.WriteLine(parent.Status);
Thread.Sleep(4000);
Console.WriteLine(parent.Status);
Console.ReadKey();
}
static void ParentTask(){
Console.WriteLine("task id "+Task.CurrentId);
var child = new Task(ChildTask);
child.Start();
Thread.Sleep(1000);
Console.WriteLine("parent started child , parent end");
}
static void ChildTask(){
Console.WriteLine("child");
Thread.Sleep(5000);
Console.WriteLine("child finished ");
}
线程问题-争用条件
public class StateObject{
private int state = 5;
public void ChangeState(int loop){
if(state==5){
state++;//6
Console.WriteLine("State==5:"+state==5+" Loop:"+loop);//false
}
state = 5;
}
}
static void RaceCondition(object o ){
StateObject state = o as StateObject;
int i = 0;
while(true){
state.ChangeState(i++);
}
}
static void Main(){
var state = new StateObject();
for(int i=0;i<20;i++){
new Task(RaceCondition,state).Start();
}
Thread.Sleep(10000);
}
使用lock(锁)解决争用条件的问题
static void RaceCondition(object o ){
StateObject state = o as StateObject;
int i = 0;
while(true){
lock(state){
state.ChangeState(i++);
}
}
}
//另外一种方式是锁定StateObject中的state字段,但是我们的lock语句只能锁定个引用类型。因此可以定义一个object类型的变量sync,将它用于lock语句,每次修改state的值的时候,都使用这个一个sync的同步对象。就不会出现争用条件的问题了。下面是改进后的ChangeState方法
private object sync = new object();
public void ChangeState(int loop){
lock(sync){
if(state==5){
state++;
Console.WriteLine("State==5:"+state==5+" Loop:"+loop);
}
state = 5;
}
}
线程问题-死锁
public class SampleThread{
private StateObject s1;
private StateObject s2;
public SampleThread(StateObject s1,StateObject s2){
this.s1= s1;
this.s2 = s2;
}
public void Deadlock1(){
int i =0;
while(true){
lock(s1){
lock(s2){
s1.ChangeState(i);
s2.ChangeState(i);
i++;
Console.WriteLine("Running i : "+i);
}
}
}
}
public void Deadlock2(){
int i =0;
while(true){
lock(s2){
lock(s1){
s1.ChangeState(i);
s2.ChangeState(i);
i++; Console.WriteLine("Running i : "+i);
}
}
}
}
}
var state1 = new StateObject();
var state2 = new StateObject();
new Task(new SampleTask(s1,s2).DeadLock1).Start();
new Task(new SampleTask(s1,s2).DeadLock2).Start();