基础拾遗
基础拾遗------特性详解
基础拾遗------webservice详解
基础拾遗------redis详解
基础拾遗------反射详解
基础拾遗------委托、事件详解
基础拾遗------接口详解
基础拾遗------泛型详解
基础拾遗-----依赖注入
基础拾遗-----数据注解与验证
基础拾遗-----mongoDB操作
基础拾遗----RabbitMQ
基础拾遗---CLR
基础拾遗----多线程
前言
我们知道c# 程序是自上而下的,但有的时候部分程序使用时间较长比如下载文档什么的。这是就可以用到线程。线程可以理解为是程序的执行路径,每个线程都定义了一个独特的控制流。如果您的应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。
1.线程的生命周期
线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。
下面列出了线程生命周期中的各种状态:
未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。
就绪状态:当线程准备好运行并等待 CPU 周期时的状况。
不可运行状态:下面的几种情况下线程是不可运行的:
已经调用 Sleep 方法
已经调用 Wait 方法
通过 I/O 操作阻塞
死亡状态:当线程已完成执行或已中止时的状况。
2.多线程的优缺点
2.1.优点
- 可以使用线程将代码同其他代码隔离,提高应用程序的可靠性。
- 可以使用线程来简化编码。
- 可以使用线程来实现并发执行。
- 可以提高CPU的利用率
2.2.缺点
- 线程开的越多,内存占用越大。
- 协调和管理代码的难度加大,需要CPU时间跟踪线程。
- 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。
- 销毁线程需要了解可能发生的问题并对那些问题进行处理。
3.线程的实现
3.1.异步委托
关于委托基础拾遗------委托、事件详解这有详细介绍,我们都知道调用委托Delegate()或者Delegate?.Invoke()。进行执行,但是主线程的代码是从上至下进行执行的,那么我们想要委托方法进行一个新的线程只需BeginInvoke生成异步方法调用即可。
3.3.1.实现
public delegate void ThreadDelegate(); static void MethodDelegata() { Console.WriteLine("MethodDelegata"); } static void Main(string[] args) { ThreadDelegate d = MethodDelegata; //BeginInvoke 两个参数一个是执行完后回调方法,一个是返回结果,如委托有参数载气前方添加即可。 d.BeginInvoke(null,null); Console.WriteLine("Main"); Console.ReadKey(); }
执行结果如下
3.1.1.获取线程返回值
线程执行时有可能执行时间过长,如果我们要获取线程的返回值,这是就需要不回线程的状态和利用线程的回调方法。
- 检测等待线程状态
public delegate int ThreadDelegate(int i); static int MethodDelegata(int i) { Console.WriteLine("MethodDelegata" + i); Thread.Sleep(1000); return 100; } static void Main(string[] args) { ThreadDelegate d = MethodDelegata; //BeginInvoke 两个参数一个是执行完后回调方法,一个是返回结果 IAsyncResult ar = d?.BeginInvoke(1, null, null);//获取线程执行状态 Console.WriteLine("Main"); while (!ar.IsCompleted) {//线程是否已执行完成,未完成执行 Console.WriteLine("."); Thread.Sleep(10);//减少线程监测频率 } int res = d.EndInvoke(ar);//获取线程的返回值 Console.WriteLine(res); Console.ReadKey(); }
结果如下
我们如果不用while 的方式去等待方法执行结束,可以 ar.AsyncWaitHandle.WaitOne(1000); 但我们预估执行时间如果小于实际执行时间的化,返回值就获取不到了。
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000); if (isEnd) { int res = d.EndInvoke(ar);//获取线程的返回值 Console.WriteLine(res); }
- 利用 d?.BeginInvoke(1, callBack, object) 回调方法
public delegate int ThreadDelegate(int i); static int MethodDelegata(int i) { Console.WriteLine("MethodDelegata" + i); Thread.Sleep(1000); return 100; } static void Main(string[] args) { ThreadDelegate d = MethodDelegata; //BeginInvoke 两个参数一个是执行完后回调方法,一个是返回结果 IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//获取线程执行状态 Console.WriteLine("Main"); Console.ReadKey(); } ////// 结束回调方法 /// /// private static void CallBack(IAsyncResult ar) { var obj=ar.AsyncState as ThreadDelegate; int res = obj.EndInvoke(ar); Console.WriteLine("线程结束,结果为:"+res); }
我们通过lamda表达式优化一下上面的代码
static void Main(string[] args) { ThreadDelegate d = MethodDelegata; //BeginInvoke 两个参数一个是执行完后回调方法,一个是返回结果 //IAsyncResult ar = d?.BeginInvoke(1, CallBack, d);//获取线程执行状态 d?.BeginInvoke(1, ar => { int res = d.EndInvoke(ar); Console.WriteLine("线程结束,结果为:" + res); }, null); Console.WriteLine("Main"); Console.ReadKey(); }
3.2.Thread 类
3.2.1.不带参数
static void MethodThread() { Console.WriteLine("MethodDelegata");//第二个参数最多执行时间 Thread.Sleep(1000); } static void Main(string[] args) { Thread t = new Thread(MethodThread);//创建了thread 对象单位启动 //Thread t = new Thread(()=> { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000);});//可直接用lamda表达式 t.Start(); Console.WriteLine("Main"); Console.ReadKey(); }
3.2.2.带参数
Start(obj) 传参:定义方法如果有参数必须object
static void MethodThread(object s) { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } static void Main(string[] args) { //创建了thread 对象单位启动 Thread t = new Thread(MethodThread); t.Start("wokao");//传递参数 Console.WriteLine("Main"); Console.ReadKey(); }
对象传参:定义存放数据和线程方法的类
class Program { static void Main(string[] args) { //创建了thread 对象单位启动 ClassThead c = new ClassThead("1"); Thread t = new Thread(c.MethodThread); t.Start();//传递参数 Console.WriteLine("Main"); Console.ReadKey(); } } public class ClassThead { private string wr; public ClassThead(string s) { this.wr = s; } public void MethodThread() { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } }
3.2.3 前台线程和后台线程
- 前台线程:只要存在有一个前台线程在运行,应用程序就在运行。
- 后台线程:应用程序关闭时,如果后台线程没有执行完,会被强制性的关闭
- 默认情况下,用Thread类创建的线程是前台线程,线程池中的线程总是后台线程。
- thread.IsBackground = true; 设置为后台程序
static void MethodThread() { Console.WriteLine("MethodDelegata"); Thread.Sleep(10000); Console.ReadKey(); } static void Main(string[] args) { Thread t = new Thread(MethodThread); t.IsBackground = true;//当main执行结束后,不管t是否执行结束程序都关闭 t.Start();//传递参数 Console.WriteLine("Main"); }
thread.Abort() 终止线程的执行。调用这个方法,会抛出一个ThreadAbortException类型的异常。
thread.Join() 将当前线程睡眠,等待thread线程执行完,然后再继续执行当前线程。
3.3.线程池threadPool
上面已经说了线程是为后台线程,在这多线程的操作推荐使用线程池线程而非新建线程。因为就算只是单纯的新建一个线程,这个线程什么事情也不做,都大约需要1M的内存空间来存储执行上下文数据结构,并且线程的创建与回收也需要消耗资源,耗费时间。而线程池的优势在于线程池中的线程是根据需要创建与销毁,是最优的存在。但是这也有个问题,那就是线程池线程都是后台线程,主线程执行完毕后,不会等待后台线程而直接结束程序。
//如果带参数必须为object static void MethodThreadPool(object obj) { Console.WriteLine("MethodDelegata"+Thread.CurrentThread.ManagedThreadId);//当前线程id Thread.Sleep(1000); } static void Main(string[] args) { ThreadPool.QueueUserWorkItem(MethodThreadPool);// 必须带参数 ThreadPool.QueueUserWorkItem(MethodThreadPool); ThreadPool.QueueUserWorkItem(MethodThreadPool); ThreadPool.QueueUserWorkItem(MethodThreadPool); ThreadPool.QueueUserWorkItem(MethodThreadPool); Console.WriteLine("Main"); Console.ReadKey(); }
3.4. Task
- Task是架构在Thread之上的,也就是说任务最终还是要抛给线程去执行。
- Task跟Thread不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制
- 可以将任务入队到线程池中异步执行。
- 线程池入队的任务无法取消
- 没有回调方法,可以使用委托实现回调
3.4.1.任务的定义
方法1
var t1 = new Task(() => TaskMethod("Task 1")); t1.Start(); Task.WaitAll(t1);//等待所有任务结束
方法2
Task.Run(() => TaskMethod("Task 2"));
方法3
Task.Factory.StartNew(() => TaskMethod("Task 3")); 直接异步的方法 //或者 var t3=Task.Factory.StartNew(() => TaskMethod("Task 3")); Task.WaitAll(t3);//等待所有任务结束
案列
static void Main(string[] args) { var t1 = new Task(() => TaskMethod("Task 1")); var t2 = new Task(() => TaskMethod("Task 2")); t2.Start(); t1.Start(); Task.WaitAll(t1, t2); Task.Run(() => TaskMethod("Task 3")); Task.Factory.StartNew(() => TaskMethod("Task 4")); //标记为长时间运行任务,则任务不会使用线程池,而在单独的线程中运行。 Task.Factory.StartNew(() => TaskMethod("Task 5"), TaskCreationOptions.LongRunning); Console.WriteLine("主线程执行业务处理."); //创建任务 Task task = new Task(() => { Console.WriteLine("使用System.Threading.Tasks.Task执行异步操作."); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } }); //启动任务,并安排到当前任务队列线程中执行任务(System.Threading.Tasks.TaskScheduler) task.Start(); Console.WriteLine("主线程执行其他处理"); task.Wait(); Thread.Sleep(TimeSpan.FromSeconds(1)); Console.ReadLine(); } static void TaskMethod(string name) { Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}", name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); }
3.4.2.async/await
async是contextual关键字,await是运算符关键字。
async/await 结构可分成三部分:
- 调用方法:该方法调用异步方法,然后在异步方法执行其任务的时候继续执行;
- 异步方法:该方法异步执行工作,然后立刻返回到调用方法;
- await 表达式:用于异步方法内部,指出需要异步执行的任务。一个异步方法可以包含多个 await 表达式(不存在 await 表达式的话 IDE 会发出警告)。
class Program { async static void AsyncFunction() { await Task.Delay(1); Console.WriteLine("使用System.Threading.Tasks.Task执行异步操作."); for (int i = 0; i < 10; i++) { Console.WriteLine(string.Format("AsyncFunction:i={0}", i)); } } public static void Main() { Console.WriteLine("主线程执行业务处理."); AsyncFunction(); Console.WriteLine("主线程执行其他处理"); for (int i = 0; i < 10; i++) { Console.WriteLine(string.Format("Main:i={0}", i)); } Console.ReadLine(); } }
4.线程争用与死锁
class Program { static void ChangeState(object obj) { ClassThead c = obj as ClassThead; while (true) { c.MethodThread(); } } //如果带参数必须为object static void Main(string[] args) { ClassThead c = new ClassThead(); Thread t = new Thread(ChangeState); t.Start(c); Console.WriteLine("Main"); Console.ReadKey(); } } public class ClassThead { private int state = 6; public void MethodThread() { state++; if (state == 6) { Console.WriteLine("MethodDelegata"); Thread.Sleep(1000); } state = 6; } }
可以从上面的方法中看到执行结果为空,虽然他在执行但是state一直都是>6的。所以是不执行的。
但如果开启两个线程的结果是什么呢?
是执行的因为多个线程有可能是在执行时另一个线程给他赋值了。所以我们就要给对象加锁
static void ChangeState(object obj) { ClassThead c = obj as ClassThead; while (true) { lock (c) { c.MethodThread(); } } }
注:但是有可能会出现线程争用一直等待的情况,所以在编程过程设计好锁的顺序