多线程编程必须了解线程和进程的概念
进程(process):应用程序的实例要使用的资源的集合。每个进程被赋予了一个虚拟地址空间,确保在一个进程中使用的代码和数据无法由另一个进程访问。
进程池(process pool)管理进程负责创建资源进程,把工作交给空闲资源进程处理,回收已经处理完工作的资源进程。
线程(thread):程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,及不同的线程可以执行相同的函数。
线程池(thread pool)与进程池类似,如果一个进程内创建销毁线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。避免了重复创建线程造成的开销。
协程(coroutine)协程不是系统级线程,很多时候协程被称为“轻量级线程”是独立于操作系统的进程线程外开发的实现并发的方式,也就是说可以通过单线程实现并发。
多线程编程优缺点,
优点:可以提高CPU利用率。
缺点:线程越多占用内存越多;线程之间对共享资源会相互影响,必须解决竞用共享资源问题;
一、用Thread和ThreadPool对象对线程进行管理
private void button5_Click(object sender, EventArgs e) { //new一个新的线程对象(他的构造函数中需要传一个回调函数进去,可以单写一个方法, //也可以使用lambda表达式) // Thread tmain = new Thread(Cooking); //设为后台线程,主线成结束后台线程自动结束;前台线程在主线程结束后继续执行才结束 tmain.IsBackground = true; //线程优先级枚举设定,此时最高,在线程池中会优先开始 tmain.Priority = ThreadPriority.Lowest; //使用lambda表达式 //Thread t = new Thread(() => //{ // Thread.Sleep(3000); // MessageBox.Show("青菜炒好了"); // Thread.Sleep(5000); // MessageBox.Show("荤菜做好了"); //}); //给线程命名 tmain.Name = "厨师"; //线程开启 tmain.Start(); //Thread Join()使用,在两个线程调用之间使用,阻塞当前线程, //直到另一个线程结束。将两个交替的线程合并为顺序执行的线程。 //比如这里在主线程中调用后台子线程的方法Join(), //直到后台子线程执行完毕,主线程才继续执行 tmain.Join(); } static void DownLoadFile(object sender) { MessageBox.Show("开始下载。。。线程ID" + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); MessageBox.Show("下载完成"); } private void Cooking() { Thread.Sleep(3000); MessageBox.Show("青菜炒好了"); Thread.Sleep(5000); MessageBox.Show("荤菜做好了"); } private void button7_Click(object sender, EventArgs e) { MessageBox.Show("菜马上好了"); } private void button8_Click(object sender, EventArgs e) { //通过线程池类ThreadPool开启线程 //通过线程池对象将回调函数排入队列,等待执行 ThreadPool.QueueUserWorkItem(DownLoadFile); //使用ThreadPool线程池开启一个线程 ThreadPool.QueueUserWorkItem((p) => { MessageBox.Show(String.Format("是否线程池线程:{0}", Thread.CurrentThread.IsThreadPoolThread)); MessageBox.Show(String.Format("开启了一个线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId)); }); Thread.Sleep(5000); MessageBox.Show("主线程结束"); }
二、Task或TaskFactory方式多线程编程(任务并行编程)
Task跟线程池ThreadPool的功能类似,不过写起来更为简单,直观。
使用Task来进行操作。可以跟线程一样可以轻松的对执行的方法进行控制。
Task 类的表示单个操作不返回一个值,通常以异步方式执行。
可以使用 Status 属性,以及 IsCanceled, ,IsCompleted, ,和 IsFaulted 属性,以确定任务的状态。
private void button9_Click(object sender, EventArgs e) { //直接使用Task开启一个任务(其实也是一个线程) //Task的简略生命周期 //默认初始化任务 //任务调度器分配线程给任务执行 //任务执行完毕 Task task1 = new Task(new Action(() => { MessageBox.Show(String.Format("是否线程池线程:{0}", Thread.CurrentThread.IsThreadPoolThread)); MessageBox.Show(String.Format("开启了一个线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId)); })); //任务开始 task1.Start(); //任务等待 task1.Wait(); //也可以使用Task.Factory开启一个任务 Task.Factory.StartNew(() => { MessageBox.Show(String.Format("是否线程池线程:{0}", Thread.CurrentThread.IsThreadPoolThread)); MessageBox.Show(String.Format("开启了一个线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId)); }); //或者new一个Task工厂来开启一个任务 TaskFactory tf = new TaskFactory(); tf.StartNew(() => { MessageBox.Show(String.Format("是否线程池线程:{0}", Thread.CurrentThread.IsThreadPoolThread)); MessageBox.Show(String.Format("开启了一个线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId)); }); Thread.Sleep(2000); MessageBox.Show("主线程结束"); //CancellationToken取消令牌 //创建一个CancellationTokenSource对象 var cts = new CancellationTokenSource(); //直接使用Task开启一个任务<>里面是返回结果的数据结构 var Ctask = new Task<int>(() => { return TaskAction("task", 10, cts.Token); }); //任务开始 Ctask.Start(); Thread.Sleep(2000); //显示任务状态 MessageBox.Show(Ctask.Status.ToString()); DialogResult result = MessageBox.Show("确定停止任务吗?", "停止任务", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if(result== DialogResult.Yes) { //取消任务 cts.Cancel(); } MessageBox.Show(String.Format("任务结果:{0}", Ctask.Result)); MessageBox.Show(Ctask.Status.ToString()); } static int TaskAction(string name, int seconds, CancellationToken token) { MessageBox.Show(String.Format("任务:{0} 正在执行,线程ID:{1}", name, Thread.CurrentThread.ManagedThreadId)); for (int i = 0; i < seconds; i++) { Thread.Sleep(1000); //判断是否已请求取消 if (token.IsCancellationRequested) { MessageBox.Show("请求取消令牌产生作用"); return -1; } } return 42 * seconds; }
//创建任务集合及返回结果 //Task<T>,Result等待任务调用完成得到结果,有Wait的作用 var tasks = new List<Task<string>>() { Task.Factory.StartNew(()=> { return "task1"; }), Task.Factory.StartNew(()=> { return "task2"; }), Task.Factory.StartNew(()=> { return "task3"; }), }; foreach (var task in tasks) { MessageBox.Show(task.Result); } }
三、异常捕获
使用Thread创建的子线程,需要在委托中捕捉,无法在上下文线程中捕捉
private void button10_Click(object sender, EventArgs e) { //创建ThreadStart的委托类型 ThreadStart threadStart = DoWork; Thread thread = new Thread(threadStart); thread.Start(); } /// <summary> /// 异常捕获需要写在这里 /// </summary> private void DoWork() { try { throw new Exception("子线程出现异常了"); } catch (Exception ex) { Trace.Assert(false, ex.Message); } }
Task中处理异常
1.仍然可以在委托中捕获异常
2.可以捕获Task.Wait() 或者 Task.Result 的 AggregateException 异常,AggregateException 是并行任务中捕获的一组异常
//使用Task的.Wait() 或者 Task.Result 的 AggregateException 异常 try { Task task1 = new Task(new Action(() => { MessageBox.Show(String.Format("是否线程池线程:{0}", Thread.CurrentThread.IsThreadPoolThread)); MessageBox.Show(String.Format("开启了一个线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId)); })); task1.Wait(); } catch(AggregateException ex) { MessageBox.Show(ex.Message); }
四、关于同步和异步的区别
通俗的讲:
同步异步 指的是在客户端
同步意味着 客户端提出了一个请求以后,在回应之前只能等待
异步意味着 客户端提出一个请求以后,还可以继续提其他请求
阻塞非阻塞 指的是服务器端
阻塞意味着 服务器接受一个请求后,在返回结果以前不能接受其他请求
非阻塞意味着 服务器接受一个请求后,尽管没有返回结果,还是可以继续接受其他请求
严肃的讲:
同步方法调用在程序继续执行之前需要等待同步方法执行完毕返回结果
异步方法则在被调用之后立即返回以便程序在被调用方法完成其任务的同时执行其它操作
所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由*调用者*主动等待这个*调用*的结果。
而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。
而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。
五、DotNET框架基类库中的同步和异步的方法调用。
因为同步方法调用会导致程序流程中途等待,所以采用同步方法的情况下往往会导致程序执行的延迟
相比来说,在某些条件下选择异步方法调用就可能更好一些
例如,有的时候程序需要给多个Web服务发出请求,还有远程处理信道(HTTP、TCP)和代理,这时就最好采用异步方法
在 .NET4.0 中 Microsoft 又为我们引入了新的异步编程模型“基于任务的异步编程模型 (TAP) ”,
并且推荐我们在开发新的多线程应用程序中首选 TAP
术语:
APM 异步编程模型, Asynchronous Programming Model
EAP 基于事件的异步编程模式, Event-based Asynchronous Pattern
TAP 基于任务的异步编程模式, Task-based Asynchronous Pattern
使用方法:
/// <summary> /// 委托必须和要调用的异步方法有相同的签名 /// </summary> /// <param name="callDuration">sleep时间</param> /// <param name="threadId">当前线程id</param> /// <returns></returns> public delegate string AsyncMethodCaller(int callDuration, out int threadId); internal class Program { /// <summary> /// 异步编程模型(APM)(使用IAsyncResult和BeginInvoke)不再是异步调用的优选方法。 /// 从.NET Framework 4.5开始,基于任务的异步模式(TAP)是推荐的异步模型。 /// 因此,而且由于异步委托的实现取决于远程处理但.NET Core不存在的功能, /// BeginInvoke和EndInvoke委托调用不.NET Core支持。 /// </summary> /// <param name="args"></param> static void Main(string[] args) { //实例化一个委托并且将相同前面的异步方法传入 AsyncMethodCaller caller = new AsyncMethodCaller(TestMethodAsync); int threadid = 0; //开启异步操作 var workTask = Task.Run(() => caller.Invoke(1000, out threadid)); for (int i = 0; i < 10; i++) { //模拟主线程耗时操作 Thread.Sleep(1000); Console.WriteLine("其它业务" + i.ToString()); } //调用EndInvoke,等待异步执行完成 Console.WriteLine("等待异步方法TestMethodAsync执行完成"); string res = workTask.Result; Console.WriteLine("Completed!"); Console.WriteLine(res); Console.Read(); } /// <summary> /// 与委托对应的方法 /// </summary> /// <param name="callDuration"></param> /// <param name="threadId"></param> /// <returns></returns> static string TestMethodAsync(int callDuration, out int threadId) { Stopwatch sw = new Stopwatch(); sw.Start(); Console.WriteLine("异步TestMethodAsync开始"); for (int i = 0; i < 5; i++) { // 模拟耗时操作 Thread.Sleep(callDuration); Console.WriteLine("TestMethodAsync:" + i.ToString()); } sw.Stop(); threadId = Thread.CurrentThread.ManagedThreadId; return string.Format("耗时{0}ms.", sw.ElapsedMilliseconds.ToString()); } }
浙公网安备 33010602011771号