.net基础—多线程(二)
Thread
在.NET中最早提供的控制线程类型的类型:System.Threading.Thread类。使用该类型可以直观地创建、控制和结束线程。下面是一个简单的多线程程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | static void Main( string [] args) { Console.WriteLine( "进入多线程模式:" ); for ( int i = 0; i < 5; i++) { Thread t = new Thread(Work); t.Start(); //开启新线程 } Console.ReadKey(); } static void Work() { Console.WriteLine($ "开始工作的线程ID:{Thread.CurrentThread.ManagedThreadId} " ); Thread.Sleep(1000); //假如处理任务耗时1s Console.WriteLine($ "结束工作的线程ID:{Thread.CurrentThread.ManagedThreadId} " ); } |
该段代码是在主线程中创建5个线程,每个线程进行独立的工作互不干涉。代码执行结果如下:
Thread中提供了很多控制线程的方法,例如:终止本线程(Abort)、暂停(Suspend)、恢复(Resume)、阻塞调用线程(Join)等等。
ThreadPool
Thread是.NET Framework 1.0中推出实现多线程的方案,它是直接从计算机系统里抓取的线程,这就存在很多问题,例如:频繁的创建和销毁线程、无节制的创建线程等。所以微软在.NET Framework 2.0中推出ThreadPool(线程池),它是给开发人员提供一个线程容器,在使用多线程的时从容器中抓取线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | internal class Program { static void Main( string [] args) { Console.WriteLine( "进入多线程模式:" ); for ( int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem( new WaitCallback(Work)); } Console.ReadKey(); } static void Work( object stateInfo) { Console.WriteLine($ "开始工作的线程ID:{Thread.CurrentThread.ManagedThreadId} " ); // Console.WriteLine($ "结束工作的线程ID:{Thread.CurrentThread.ManagedThreadId} " ); } } |
上面这段就是使用ThreadPool实现多线程,代码执行结果如下:
Task
Task是从 .NET Framework 4 推出的实现多线程的方法,Task 的使用非常简单,一行代码就可以开始一个异步任务,代码如下:
1 2 3 4 5 6 | static void Main( string [] args) { Console.WriteLine($ "Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}" ); Task.Run(() => Console.WriteLine($ "Task 开启的线程,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}" )); Console.ReadKey(); } |
向新创建的Task中传递参数的示例代码:
1 2 3 4 5 6 7 8 9 10 11 | static void Main( string [] args) { Console.WriteLine($ "Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}" ); string temp = "Tanyongjun" ; Task t = new Task(obj => Console.WriteLine($ "Task 开启的线程,传递进来的参数:{obj},当前线程的ID:{Thread.CurrentThread.ManagedThreadId}" ), temp); t.Start(); Console.ReadKey(); } |
返回一个 int 类型结果的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | static void Main( string [] args) { Console.WriteLine($ "Task 示例,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}" ); Task< int > t = new Task< int >(() => { int sum = 0; for ( int i = 0; i < 10; i++) { sum += i; } return sum; }); t.Start(); Console.WriteLine($ "使用Task开启新线程计算的结果:{t.Result}" ); Console.ReadKey(); } |
使用 TaskFactory 开始异步任务
1 2 3 4 5 6 7 8 9 10 11 12 13 | static void Main( string [] args) { List<Task< int >> tasks = new List<Task< int >>(); TaskFactory factory = new TaskFactory(); tasks.Add(factory.StartNew< int >(() => { return 123; })); tasks.Add(factory.StartNew< int >(() => { return 456; })); foreach ( var item in tasks) { Console.WriteLine($ "返回值:{item.Result}" ); } Console.Read(); } |
上面的代码使用 TaskFactory 创建并运行了两个异步任务,同时把这两个任务加入了任务列表 tasks 中,然后使用foreach遍历出执行的结果。代码执行效果如下:
用Task来模拟一个项目开发的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | static void Main( string [] args) { Console.WriteLine($ "项目经理启动一个项目。线程ID:{Thread.CurrentThread.ManagedThreadId} " ); Console.WriteLine($ "前期准备工作。线程ID:{Thread.CurrentThread.ManagedThreadId} " ); Console.WriteLine($ "开始编程。线程ID:{Thread.CurrentThread.ManagedThreadId} " ); List<Task> tasks = new List<Task>(); //存放新开启的Task tasks.Add(Task.Run(() => Coding( "张三" , "后端" ))); tasks.Add(Task.Run(() => Coding( "李四" , "前端" ))); tasks.Add(Task.Run(() => Coding( "王五" , "测试" ))); // 阻塞当前线程,等着某一个任务执行完成后,才进入下一行。 Task.WaitAny(tasks.ToArray()); Console.WriteLine($ "************** 项目里程碑。。线程ID:{Thread.CurrentThread.ManagedThreadId} **************" ); //等待1秒之后,执行某个动作 //Task.WaitAll(tasks.ToArray(), 1000); //Console.WriteLine($"************** 已经等待了1秒啦。。线程ID:{Thread.CurrentThread.ManagedThreadId} **************"); // 阻塞当前线程,等着全部任务完成后,才进行下一行。 Task.WaitAll(tasks.ToArray()); Console.WriteLine($ "部署到正式环境,上线使用。线程ID:{Thread.CurrentThread.ManagedThreadId} " ); Console.Read(); } private static void Coding( string name, string project) { Console.WriteLine($ "Coding {name} 开始 {project}。线程ID:{Thread.CurrentThread.ManagedThreadId}。" ); int temp = 0; for ( int i = 0; i < 1000000000; i++) { temp += i; } Console.WriteLine($ "Coding {name} 结束 {project}。线程ID:{Thread.CurrentThread.ManagedThreadId}。" ); } |
代码执行效果如下:
Task 内部提供多种多样的基于队列的链式任务管理方法,通过使用这些快捷方式,可以让异步队列有序的执行,例如:ContinueWith(),ContinueWhenAll(),ContinueWhenAny(),WaitAll(),WaitAny(),WhenAll(),WhenAny()。
线程中的异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | static void Main( string [] args) { try { TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); for ( int i = 0; i < 5; i++) { int k = i; Action< object > act = t => { Thread.Sleep(1000); if (k == 3) throw new Exception( "出现异常啦!" ); Console.WriteLine($ "程序正常 -- {k}。" ); }; taskList.Add(taskFactory.StartNew(act, k)); } } catch (AggregateException aex) { foreach ( var item in aex.InnerExceptions) { Console.WriteLine(item.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } |
上面的代码执行结果:
在vs调试的时候能看到出现异常,但在从执行结果上看不到异常信息。这是因为线程里面的异常是抓不到的,它已经脱离try/catch 的范围。所以在新的线程内部添加try/catch ,也就是Action 具体操作内容加上try/catch,让Action不会出现异常。
上面的代码改动如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | static void Main( string [] args) { try { TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); for ( int i = 0; i < 5; i++) { int k = i; Action< object > act = t => { try { Thread.Sleep(1000); if (k == 3) throw new Exception( "出现异常啦!" ); Console.WriteLine($ "程序正常 -- {k}。" ); } catch (Exception ex) { Console.WriteLine(ex.Message); } }; taskList.Add(taskFactory.StartNew(act, k)); } } catch (AggregateException aex) { foreach ( var item in aex.InnerExceptions) { Console.WriteLine(item.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } |
执行效果:
线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | static void Main( string [] args) { TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); List< int > intList = new List< int >(); int count = 0; for ( int i = 0; i < 10000; i++) { int k = i; taskList.Add(taskFactory.StartNew(() => { count += 1; intList.Add(k); })); } Task.WaitAll(taskList.ToArray()); Console.WriteLine($ "count={count}" ); Console.WriteLine($ "intList中元素的个数={intList.Count()}" ); Console.ReadKey(); } |
执行效果:
从执行后的结果来看两个都不是10000,这是因为有的线程丢失了。
多个线程同时操作一个变量(包括:都能访问的局部变量、全局变量、数据库中的一个值),就会有某个操作会被覆盖的可能。
想要解决上面出现的问题,我们可以通过加lock 来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | //private:防止外面使用;static:保障全局唯一;readonly:不能改动;object:引用类型 private static readonly object threadLock = new object (); static void Main( string [] args) { TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); List< int > intList = new List< int >(); int count = 0; for ( int i = 0; i < 10000; i++) { int k = i; taskList.Add(taskFactory.StartNew(() => { lock (threadLock) { // 加lock 后就能保障任意时刻只有一个线程来执行该代码段 count += 1; intList.Add(k); } //lock (this) //{ // // 使用this 要注意每次实例化是不同的锁,同一个实例是相同的锁。 // // 但是这个实例别人也能访问到,别人也能锁定。 所以一般不要使用 //} })); } Task.WaitAll(taskList.ToArray()); Console.WriteLine($ "count={count}" ); Console.WriteLine($ "intList中元素的个数={intList.Count()}" ); Console.ReadKey(); } |
执行效果:
使用lock 能解决线程线程安全,因为使用lock后只有一个线程可以进去,没有并发。虽然能解决问题,但是牺牲了性能。所以在使用lock来解决线程冲突的问题是要尽量缩小lock的范围。这种问题的最佳解决方案是,进行数据拆分,避免冲突。
await/async
在.NET Framework4.5框架、C#5.0语法中,新增加了async和await两个关键字。
async 用来标记一个方法为异步方法,方法体内需结合 await 关键字使用。异步方法命名规则通常以Async结尾。await 关键字只能在异步方法中使用。它出现在 Task 前面。
async和await 一般都是成对出现的,只有async 是没有意义的,只有await 就会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | static void Main( string [] args) { Console.WriteLine( "执行前Main的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); TestAsync(); Console.WriteLine( "执行结束Main的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); Console.ReadKey(); } private static async void TestAsync() { Console.WriteLine( "执行前TestAsync的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); await Task.Run(()=> { Console.WriteLine( "执行 Action 中Sleep之前 的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); Thread.Sleep(3000); Console.WriteLine( "执行 Action 中Sleep之后 的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); }); Console.WriteLine( "执行结束TestAsync的线程ID:" + Thread.CurrentThread.ManagedThreadId.ToString()); } |
执行效果:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了