线程,任务和同步

线程

对于所有需要等待的操作,例如移动文件,数据库和网络访问都需要一定的时间,此时就可以启动一个新的线程,同时完成其他任务。一个进程的多个线程可以同时运行在不同的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();

 

你可能感兴趣的:(C#基础)