线程
线程与进程的区别:
进程:是计算机中已运行的实体。在面向线程设计的系统(当代多数系统)中,进程本身不是基本运行单位,而是线程的容器。
线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。也是独立调度和分派的基本单位。
一个进程可以由多个线程辅助执行。
开启线程的三种方式
1.通过异步委托开启线程
通过Func委托创建一个委托变量来指向我们需要使用的方法,使用BeginInvoke方法开启线程。
//需要委托的方法
static int Test(int i,string str)
{
Console.WriteLine("Test"+i+str);
return 100;
}
static void Main(string[] args)
{
//通过委托开启线程
Func<int,string,int> a = Test;
IAsyncResult可以取得当前线程的状态
IAsyncResult ar = a.BeginInvoke(100,"lymon",null, null); //开启一个新的线程去执行a所引用的方法
Console.WriteLine("Main");
//检测线程是否结束
while (ar.IsCompleted == false)
{
Console.Write(".");
Thread.Sleep(10); //控制子线程的检测频率
}
int res = a.EndInvoke(ar); //取得异步线程的返回值
//可以认为线程是同时执行的(异步执行)
Console.WriteLine(res);
结束线程的三种方法
- 通过while循环检测
使用isComplete方法用死循环判断线程是否结束,如果结束的话,使用EndInvoke方法取得线程的返回值
while (ar.IsCompleted == false)
{
Console.Write(".");
Thread.Sleep(10); //控制子线程的检测频率
}
int res = a.EndInvoke(ar); //取得异步线程的返回值
- 通过等待句柄检测
使用AsyncWaitHandle.WaitOne方法判断在规定时间内线程是否结束,将结果返回一个布尔值
//检测线程是否结束的另一种方法
//1000毫秒是超时时间,如果1000毫秒过后,线程还没结束,则会返回false,否则返回true
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);
if (isEnd)
{
int res = a.EndInvoke(ar);
Console.WriteLine(res);
}
- 通过回调函数检测
前面开启线程的BeginInvoke方法里,a.BeginInvoke(100,”lymon”,null, null);传递的参数里最后两个null,第一个null参数是委托类型的参数,表示回调函数,当线程结束的时候会调用这个委托指向的方法;第二个null参数是用来给回调函数传递数据的。
static void Main(string[] args)
{
//通过回调函数检测线程结束
Func<int, string, int> a = Test;
//后两个参数的第一个是委托类型的参数,表示回调函数;第二个参数是用来给回调函数传递数据的
IAsyncResult ar = a.BeginInvoke(100, "zhuang", OnCallBack, a);
//回调函数改用Lambda表达式
//通过Lambda表达式取得,最后一个参数不用传递数据
a.BeginInvoke(100, "zhuang", ar =>
{
int res = a.EndInvoke(ar);
Console.WriteLine(res+"在Lambda表达式中取得");
}, 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);
Console.WriteLine(res+"在回调函数中取得");
}
}
2.通过Thread类发起线程
使用Thread类创建Thread对象
//带参数的方法
static void DownLoad(object fileName)
{
Console.WriteLine("开始下载"+fileName+Thread.CurrentThread.ManagedThreadId); //获取当前线程的ID
Thread.Sleep(2000);
Console.WriteLine("下载完成");
}
static void Main(string[] args)
{
//常规的发起线程方式
Thread t = new Thread(DownLoad); //创建Thread对象,线程没有启动
t.Start(); //线程开始启动
//Lambda表达式
Thread t = new Thread(() =>
{
Console.WriteLine("开始下载" + Thread.CurrentThread.ManagedThreadId); //获取当前线程的ID
Thread.Sleep(2000);
Console.WriteLine("下载完成");
});
t.Start();
//当有参数的时候,想要传递参数需要在开始线程时传递
Thread t = new Thread(DownLoad);
t.Start("xxx.txt"); //在开始线程这里传递参数
Console.ReadKey();
}
还可以创建一个MyThread类用于定义自己想定义的参数
//自己的MyThread类
class MyThread
{
private string fileName;
private string filePath;
public MyThread(string fileName,string filePath)
{
this.fileName = fileName;
this.filePath = filePath;
}
public void DownLoad()
{
Console.WriteLine("开始下载"+filePath+fileName);
Thread.Sleep(2000);
Console.WriteLine("下载完成");
}
}
static void Main(string[] args)
{
MyThread mt = new MyThread("abc.txt","http://www.baidu.com");
Thread t = new Thread(mt.DownLoad);
t.Start();
}
3.通过线程池开启线程
当我们有很多方法需要同时进行,再一个个创建线程就会很麻烦,我们可以通过线程池中取得线程使用。
C#中有ThreadPool类来管理线程,它的最大线程数是可以配置的,双核CPU中默认线程数为1023个工作线程和1000个IO线程。
线程池的特性:
- 线程池中的所有线程都是后台线程,不能设置为前台线程
- 线程池中的线程无法修改线程优先级和名称
- 线程池中的线程只用于时间较短的任务,如果线程需要一直运行直到程序结束,则应该使用Thread类创建线程。
使用线程池开启线程:
static void ThreadMethod(Object a)
{
Console.WriteLine("线程开始"+Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.WriteLine("线程结束");
}
static void Main(string[] args)
{//开启线程的方法
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
Console.ReadKey();
}
关于线程的其他概念:
前台线程、后台线程
Thread类创建的线程默认都是前台线程。
当前台线程还没结束的时候,程序就不会结束,而当前台线程结束的时候,后台线程无论有没有执行完,程序都会停止并且关闭后台线程。线程优先级
线程是由操作系统调度的,当有很多线程需要执行的之后,线程调度器会根据线程优先级去判断先去执行哪一个线程,如果优先级全都相同,则使用循环调度规则逐个执行每个线程。线程状态
当我们调用Thread类的Start方法,线程并不是马上进入Running状态,而是处于Unstarted状态,当线程调度器选择了这个线程,它的状态才会修改为Running状态。使用Sleeping则是使线程进入WaitSleepJoin状态。
我们可以使用Thread对象的Abort方法来停止线程,它会在终止线程中抛出一个ThreadAbortException方法,我们可以trycatch这个异常来处理线程结束后的一些清理工作。
当我们不确定线程结束的事件要多久的时候,可以使用Join方法,停止当前线程,设置为WaitSleepJoin状态,将目标线程执行完之后,再开始当前线程。
t.Join(); //使当前线程睡眠,等待加入的新线程执行完再继续执行下面的代码
任务
任务的使用方法和Thread是一样的,创建任务有两个方法
1.Task直接创建
2.TaskFactory创建
static void ThreadMethod()
{
Console.WriteLine("任务开始");
Thread.Sleep(2000);
Console.WriteLine("任务结束");
}
static void Main(string[] args)
{
//创建方法1
Task t = new Task(ThreadMethod);
t.Start();
//创建方法2
TaskFactory tf = new TaskFactory();
//使用StartNew方法之后不需要使用t.Start()
Task t = tf.StartNew(ThreadMethod);
Console.WriteLine("Main");
Console.ReadKey();
}
Task和Thread区别
Task就像是一个被封装好的Thread类,但是Task是能够保证一定被执行完成的。
当程序中,Task1是依赖Task2(Task2执行完才能执行Task1)的时候可以使用ContinueWith方法。
Task的层次结构
我们在一个任务中启动一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行。如果父任务执行完而子任务没有执行完,父任务的状态设置为WaitingForChildrenToComplete,只有子任务执行完,父任务状态才会变成RunToCompletion。