一.相关概念
1.一个进程可以有多个线程
(进程看成一个工厂,线程看成工人)
2.一个线程里面语句的执行,是从上到下的,前一个未完成,不能执行下一个,会影响效率。所以可以用多线程来解决这个问题。
3.一般我们会为比较耗时的操作开启新线程,比如下载
二.线程开启方式一——异步委托
class Program
{
static void Test()
{
Console.WriteLine("test");
}
static void Main(string[] args)//一个线程里面语句的执行,是从上到下的,前一个未完成,不能执行下一个,会影响效率。所以用多线程可以解决这个问题
{
Action a = Test;
a.BeginInvoke(null,null);//开启一个新线程去执行a所引用的方法。
Console.WriteLine("main");
Console.ReadKey();
}
}
以上代码运行结果
发现先输出的是main,说明是main和test是两个线程。可以认为两个线程是同时执行的(异步执行),哪个快先输出哪个。
多个参数时:
static void Test(int i,string str)
{
Console.WriteLine(“test”+i+str);
}
对应
Actiona = Test;
a.BeginInvoke(100,“siki”,null,null);
有返回值时:得判断线程有没有结束,才可以得到返回值,因此与之前的方法不同
static int Test(int i,string str)
{
Console.WriteLine(“test”+i+str);
Thread.Sleep(100);//当前线程暂停100ms
return 100;
}
对应Func
a = Test;
IAsyncResult ar = a.BeginInvoke(10,“siki”,null, null);//取得当前线程的状态
while (ar.IsCompleted == false)//如果未完成就输出.
{
Console.Write(".");
Thread.Sleep(10);//控制线程检测频率,每10ms检测一次
}
int res = a.EndInvoke(ar);//检测是否完成
Console.WriteLine(res);
class Program
{
static int Test(int i,string str)
{
Console.WriteLine("test"+i+str);
Thread.Sleep(100);//当前线程暂停100ms
return 100;
}
static void Main(string[] args)//一个线程里面语句的执行,是从上到下的,前一个未完成,不能执行下一个,会影响效率。所以用多线程可以解决这个问题
{
Func<int,string,int> a = Test;
IAsyncResult ar = a.BeginInvoke(10,"siki",null, null);//取得当前线程的状态
Console.WriteLine("main");
while (ar.IsCompleted == false)//如果未完成就输出.
{
Console.Write(".");
Thread.Sleep(10);//控制线程检测频率,每10ms检测一次
}
int res = a.EndInvoke(ar);
Console.WriteLine(res);
Console.ReadKey();
}
}
通过等待句柄和回调函数检测委托线程的结束
class Program
{
static int Test(int i,string str)
{
Console.WriteLine("test"+i+str);
Thread.Sleep(100);//当前线程暂停100ms
return 100;
}
static void Main(string[] args)//一个线程里面语句的执行,是从上到下的,前一个未完成,不能执行下一个,会影响效率。所以用多线程可以解决这个问题
{
Func<int,string,int> a = Test;
IAsyncResult ar = a.BeginInvoke(10,"siki",null, null);//取得当前线程的状态
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);//1000MS表示超时时间,如果等了1000ms线程还没有结束的话,这个方法会返回false,结束了返回true
if (isEnd)
{
int res = a.EndInvoke(ar);
Console.WriteLine(res);
}
Console.ReadKey();
}
}
class Program
{
static int Test(int i,string str)
{
Console.WriteLine("test"+i+str);
Thread.Sleep(100);//当前线程暂停100ms
return 100;
}
static void Main(string[] args)//一个线程里面语句的执行,是从上到下的,前一个未完成,不能执行下一个,会影响效率。所以用多线程可以解决这个问题
{
Func<int, string, int> a = Test;
//倒数第二个参数是委托类型的参数,表示回调函数,当线程结束的时候会调用这个委托指向的方法.最后一个参数用来给回调函数传递数据
IAsyncResult ar = a.BeginInvoke(100, "siki", OnCallBack, a);
Console.ReadKey();
}
static void OnCallBack(IAsyncResult ar)
{
Func<int, string, int> a = ar.AsyncState as Func<int, string, int>;
int res = a.EndInvoke(ar);
}
}
class Program
{
static int Test(int i,string str)
{
Console.WriteLine("test"+i+str);
Thread.Sleep(100);//当前线程暂停100ms
return 100;
}
static void Main(string[] args)
{
Func<int, string, int> a = Test;
//IAsyncResult ar = a.BeginInvoke(100, "siki", OnCallBack, a);//可以表示为:
a.BeginInvoke(100, "siki", ar =>
{
int res = a.EndInvoke(ar);
Console.WriteLine(res + "lambda get");
}, null);
Console.ReadKey();
}
static void OnCallBack(IAsyncResult ar)
{
Func<int, string, int> a = ar.AsyncState as Func<int, string, int>;
int res = a.EndInvoke(ar);
}
通过thread类开启线程
写法一.
static void Main(string[] args){
Thread t = new Thread(() =>
{
Console.WriteLine("开始下载");
Thread.Sleep(2000);
Console.WriteLine("下载完成");
});
t.Start();
Console.ReadKey();
}
写法二.
static void DownLoad()
{
Console.WriteLine("开始下载");
Thread.Sleep(2000);
Console.WriteLine("下载结束");
}
static void Main(string[] args){
Thread t=new Thread(DownLoad);
t.Start();
Console.ReadKey();
}
当要通过Thread传递数据时
方法一:定义一个方法,通过方法中的object参数传递
static void DownLoad(object filename)//注意,这里不用string,而要用object
{
Console.WriteLine("开始下载");
Thread.Sleep(2000);
Console.WriteLine("下载结束");
}
static void Main(string[] args)
{
Thread t = new Thread(DownLoad);
t.Start("种子文件");
Console.WriteLine("main");
Console.ReadKey();
方法二:创建对象,需要的数据放在对象里面,在类中创建方法,在program执行方法
class MyThread
{
private string filename;
private string filepath;
public MyThread(string filename, string filepath)
{
this.filename = filename;
this.filepath = filepath;
}
public void DownFile()
{
Console.WriteLine("开始下载" + filepath + filepath);
Thread.Sleep(2000);
Console.WriteLine("下载完成");
}
}
class Program
{
static void Main(string[] args)
{
MyThread my = new MyThread("xxx.bt","www.cjuvr.bbs");
Thread t = new Thread(my.DownFile);//构造一个thread对象的时候,可以传递一个静态方法,也可以传递一个对象的普通方法。
t.Start();
Console.ReadKey();
}
}
后台线程和前台线程
1.判断应用程序有没有结束,判断前台线程有没有结束即可。当所有前台线程运行完毕,后台线程还没有完成的时候。所有后台线程将会被终止。
2.在默认情况下,用Tread创建的线程总是前台线程,线程池中的线程总是后台线程。可以使用IsBackground属性表示她是前台还是后台。
class Program
{
static void DownLoad(object filename)
{
Console.WriteLine("开始下载");
Thread.Sleep(2000);
Console.WriteLine("下载结束");
}
static void Main(string[] args)
{
Thread t = new Thread(DownLoad);//这个默认是前台线程
t.IsBackground = true;//设置为后台线程
t.Start("种子文件");
}
}
当把上述代码中t.IsBackground = true;注释掉时,会显示开始下载,停顿两秒,窗口再自动关闭,因为 Thread t = new Thread(DownLoad);默认是前台线程。当加上t.IsBackground = true;时,把他设置成了后台线程,当前台线程main终止后,他也自动停止了,运行时没有输出任何东西。
线程的优先级
线程由操作系统调度,一个cpu同时只能运行一个线程中的计算任务,很多线程需要执行时,会根据优先级调度。
在Thread类中,使用Priority来影响优先级,Priority属性是一个ThreadPriority定义一个值,定义的级别有:Highest、AboveNormal、BelowNormal、Lowest。
控制线程
1.通过Thread的Start方法创建线程,进入Understand状态,操作系统选择了要运行的程序,进入Running状态,使用Thread.Sleep();可以让线程休眠,进入WaitSleepJoin状态。
2.使用Thread对象的Abort()方法停止线程
3.使用Thread对象的Join()方法让线程等待,等待t线执行完,然后继续执行。
线程池
用线程池创建的线程默认是后台线程。不能修改为前台线程,不能设置优先级,只能用于时间较短的任务。
class Program
{
static void ThreadMethod(object state)
{
Console.WriteLine("start"+Thread.CurrentThread.ManagedThreadId);//获取当前线程的ID
Thread.Sleep(2000);
Console.WriteLine("end");
}
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
Console.ReadKey();
}
}
线程开启方式——任务
class Program
{
static void ThreadMethod()
{
Console.WriteLine("任务开始");
Thread.Sleep(2000);
Console.WriteLine("任务结束");
}
static void Main(string[] args)
{
/*写法一:
Task t = new Task(ThreadMethod);//传递一个需要线程去执行的方法
t.Start();*/
//写法二:
TaskFactory tf = new TaskFactory();
Task t = tf.StartNew(ThreadMethod);
Console.WriteLine("main");
Console.ReadKey();
}
}
任务的其他知识
1.t1依赖于t2,必须t2完成后,才能执行t1。通过ContinueWith来控制
2.层次结构:父任务必须等子任务执行完了,才会完成。如果父任务执行完了,子任务没有,他的状态会变成WaitingForChildrenToComplete,只有子任务也执行完了,才会变成RunToCompletion
争用条件和死锁
class My
{
private int state=5;
public void ChangeState()
{
state++;
if(state==5)
{
Console.WriteLine("state=5");
}
state=5;
}
}
class Program
{
static void ChangeState(object o)
{
My m = o as My;
while (true)
lock (m)//锁定m对象,向系统申请可不可以锁定m对象,如果m对象没有被锁定,就可以,如果被锁定,那么这个语句会暂停,直到申请到m对象。
{
m.ChangeState();//在同一时刻只有一个线程在执行这个方法
}//释放对m的锁定
}
static void Main(string[] args)
{
My m = new My();
Thread t = new Thread(ChangeState);
t.Start(m);
Console.ReadKey();
}
}
lock (m) 锁定m对象,向系统申请可不可以锁定m对象,如果m对象没有被锁定,就可以,如果被锁定,那么这个语句会暂停,直到申请到m对象。
死锁:
static void test1(){
while (true){
lock (m1){
lock(m2){
}
}
}
}
static void test2(){
while (true){
lock (m2){
lock(m1){
}
}
}
}
如上述代码,test1先申请锁定m1,并且锁定成功了,test2先申请锁定m2,也成功了,就一直相互等,形成了死锁。