C# 多线程编程及其几种方式

引言:

进程(process):应用程序的实例要使用的资源的集合。每个进程被赋予了一个虚拟地址空间,确保在一个进程中使用的代码和数据无法由另一个进程访问。

线程(thread):程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,及不同的线程可以执行相同的函数。

多线程编程优缺点,

优点:可以提高CPU利用率。

缺点:

1、线程越多占用内存越多;

2、多线程需要协调和管理,需要CPU时间跟踪线程;

3、线程之间对共享资源会相互影响,必须解决竞用共享资源问题;

4、线程太多会导致控制太复杂,可能造成很多BUG

一、用Thread类开启线程

  关键字:前台线程,后台线程,线程优先级,线程休眠,线程阻塞。

  前台线程:主线程结束后,前台线程将继续执行才结束。

  后台线程:主线程结束后,后台线程不管有没有完成也结束线程

 1 class MultiThreadingApplication {
 2         static void Main(string[] args) {
 3             //Thread thread1 = new Thread(new ThreadStart(Test1));
 4             Thread thread1 = new Thread(Test1);//线程传入无参数委托实例
 5             //Thread thread2 = new Thread(new ParameterizedThreadStart(Test2));//正常传递
 6             Thread thread2 = new Thread(Test2);//简化传递
 7             thread1.Name = "线程1";
 8             thread1.IsBackground = true;//设为后台线程,主线成结束后台线程自动结束;前台线程在主线程结束后继续执行才结束
 9             thread2.Name = "线程2";
10             thread1.Start();
11             thread2.Start("HelloWorld");
12             Thread thread3 = new Thread(() =>
13             {
14                 Console.WriteLine("线程3开始");
15                 Console.WriteLine("线程3阻塞5秒钟");
16                 Thread.CurrentThread.Join(TimeSpan.FromSeconds(5));
17                 Console.WriteLine("{0}的执行方法",Thread.CurrentThread.Name);
18                 Console.WriteLine("线程3结束");
19             });
20             thread3.Name = "线程3";
21             thread3.Priority = ThreadPriority.Highest;//线程优先级枚举设定,此时最高,在线程池中会优先开始
22             thread3.Start();
23             Console.WriteLine("Main()主函数线程结束");
24         }
25 
26         static void Test1() {
27             Console.WriteLine("线程1开始");
28             Console.WriteLine("线程1休眠2秒钟");
29             Thread.Sleep(2000);
30             Console.WriteLine("{0}调用的无参数方法",Thread.CurrentThread.Name);
31             Console.WriteLine(Thread.CurrentThread.Name+"结束");
32         }
33 
34         static void Test2(object s) {
35             Console.WriteLine("线程2开始");
36             Console.WriteLine("{0}调用的有参数方法,方法的参数是:{1}", Thread.CurrentThread.Name, s);
37             Console.WriteLine(Thread.CurrentThread.Name + "结束");
38         }
39     }  

   Thread Join()使用,在两个线程调用之间使用,阻塞当前线程,直到另一个线程结束。将两个交替的线程合并为顺序执行的线程。

比如在主线程中调用后台子线程的方法Join(),直到后台子线程执行完毕,主线程才继续执行。

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             var t = new Thread(() =>
 5             {
 6                 Console.WriteLine("后台子线程开始");
 7                 Thread.Sleep(3000);
 8                 Console.WriteLine("后台子线程");
 9                 Console.WriteLine("后台子线程结束");
10             });
11             t.IsBackground = true;
12             t.Start();
13             t.Join();//等待后台子线程执行完成,才继续执行主线程,主线程在这里被阻塞。
14             //t.Join(TimeSpan.FromSeconds(5));
15             //t.Join(5000);//主线程阻塞等待后台子线程执行5秒钟,然后继续执行主线程,5秒后不管后台子线程是否执行完成。
16             //Thread.CurrentThread.Join();//死锁情况,A线程和B线程为同一个线程,将一直阻塞。
17             Console.WriteLine("主线程结束");
18         }

 二、通过线程池类ThreadPool开启线程

 1 static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             ThreadPool.QueueUserWorkItem(DownLoadFile);
 5             //使用ThreadPool线程池开启一个线程
 6             ThreadPool.QueueUserWorkItem((p) =>
 7             {
 8                 Console.WriteLine("是否线程池线程:{0}",Thread.CurrentThread.IsThreadPoolThread);
 9                 Console.WriteLine("开启了一个线程,线程ID:{0}",Thread.CurrentThread.ManagedThreadId);
10             });
11             Thread.Sleep(5000);
12             Console.WriteLine("主线程结束");
13         }
14 
15         static void DownLoadFile(object state)
16         {
17             Console.WriteLine("开始下载...    线程ID:" + Thread.CurrentThread.ManagedThreadId);
18             Thread.Sleep(2000);
19             Console.WriteLine("下载完成!");
20         }

 三、Task或TaskFactory方式开启,叫法不同了,任务并行编程

Task需要启动任务执行,TaskFactory创建及开始执行

Task开启的是后台线程

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             //.net framework 4.0 TPL(Task Parallel Library, 简称TPL)任务并行库方式,类似于线程处理方式,抽象级别更高
 5             //任务并行,一个或多个任务同时运行
 6             //系统资源的使用效率更高,可伸缩性更好
 7             //TPL提供了一组简单丰富的 API,这些 API 支持等待、取消、继续、可靠的异常处理、详细状态、自定义计划等功能。
 8             //降低多线程编码和并行编程的复杂度,提升开发效率
 9             //使用Task开启一个任务(其实也是一个线程)
10             Task task = new Task(new Action(() =>
11             {
12                 Console.WriteLine("是否线程池线程:{0}", Thread.CurrentThread.IsThreadPoolThread);
13                 Console.WriteLine("开启了一个线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
14             }));
15             task.Start();
16 
17             Task.Factory.StartNew(() =>
18             {
19                 Console.WriteLine("是否线程池线程:{0}", Thread.CurrentThread.IsThreadPoolThread);
20                 Console.WriteLine("开启了一个线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
21             });
22 
23             TaskFactory tf = new TaskFactory();
24             tf.StartNew(() =>
25             {
26                 Console.WriteLine("是否线程池线程:{0}", Thread.CurrentThread.IsThreadPoolThread);
27                 Console.WriteLine("开启了一个线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
28             });29 
30             Thread.Sleep(2000);
31             Console.WriteLine("主线程结束");
32         }

 CancellationToken 传入取消令牌,外部控制任务内部结束

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             var cts = new CancellationTokenSource();
 5             var task = new Task<int>(()=> {
 6                 return TaskAction("task", 10, cts.Token);
 7             });
 8             task.Start();
 9             Console.WriteLine(task.Status);
10             cts.Cancel();
11             Console.WriteLine(task.Status);
12             task.Wait();
13             Thread.Sleep(2000);
14             Console.WriteLine(task.Status);
15             Console.WriteLine("task结果:{0}",task.Result);
16             Console.WriteLine("主线程结束");
17         }
18 
19         static int TaskAction(string name, int seconds, CancellationToken token) {
20             Console.WriteLine("Task:{0} is runing on Thread:{1}",name,Thread.CurrentThread.ManagedThreadId);
21             for (int i = 0; i < seconds; i++)
22             {
23                 Thread.Sleep(1000);
24                 if (token.IsCancellationRequested)
25                 {
26                     Console.WriteLine("请求取消令牌产生作用");
27                     return -1;
28                 }
29             }
30             return 42 * seconds;
31         }

 创建任务集合及返回结果

 1 static void Main(string[] args)
 2         {
 3             //Task<T>,Result等待任务调用完成得到结果,有Wait的作用
 4             var tasks = new List<Task<string>>() {
 5                 Task.Factory.StartNew(()=> {
 6                     return "task1";
 7                 }),
 8                 Task.Factory.StartNew(()=> {
 9                     return "task2";
10                 }),
11                 Task.Factory.StartNew(()=> {
12                     return "task3";
13                 }),
14             };
15 
16             foreach (var task in tasks)
17             {
18                 Console.WriteLine(task.Result);
19             }
20         }

异常捕获

通过Catch捕获Task的异常是AggregateException,一个被封装的异常,需要通过InnerException访问底层异常

推荐使用GetWaiter和GetResult方法访问Task的结果,可以获取到原始异常;

通过ContinueWith处理OnlyOnFaulted事件,捕获的异常也是一个被封装的异常,需要通过InnerException访问底层异常

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             Task<int> task = null;
 5             try
 6             {
 7                 task = Task.Run(() => TaskExceptionAction("Task1", 2));
 8                 var result = task.Result;
 9                 Console.WriteLine(result);
10             }
11             catch (Exception e)
12             {
13                 Console.WriteLine("Task 1 Exception,type:" + e.GetType() + ",Message:" + e.Message);
14             }
15             Console.WriteLine("==============================");
16             try
17             {
18                 task = Task.Run(() => TaskExceptionAction("Task2", 2));
19                 var result = task.GetAwaiter().GetResult();
20                 Console.WriteLine(result);
21             }
22             catch (Exception e)
23             {
24                 Console.WriteLine("Task 2 Exception,type:" + e.GetType() + ",Message:" + e.Message);
25             }
26 
27             var task3 = new Task<int>(() =>
28             {
29                 return TaskExceptionAction("task3", 2);
30             });
31             var continueTask = Task.WhenAll(task3);
32             continueTask.ContinueWith(t =>
33             {
34                 Console.WriteLine("Task 3 Exception,type:" + t.Exception.GetType() + ",Message:" + t.Exception.Message);
35             }, TaskContinuationOptions.OnlyOnFaulted);
36             task3.Start();
37             task3.Wait();
38             Console.WriteLine("主线程结束");
39         }
40 
41         static int TaskExceptionAction(string name, int seconds)
42         {
43             Console.WriteLine("任务:{0} 在线程:{1}上运行", name, Thread.CurrentThread.ManagedThreadId);
44             Thread.Sleep(seconds * 1000);
45             throw new Exception("Error");
46         }

 多任务的串行化

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             var queryTask = new Task<string>(() =>
 5             {
 6                 Console.WriteLine("Start queryTask!");
 7                 return "QueryResult";
 8             });
 9             var analyzeTask = queryTask.ContinueWith((queryResult) =>
10             {
11                 Console.WriteLine("Start AnalyzeTask!");
12                 return "Analyzed Data:" + queryResult.Result;
13             });
14             var reportTask = analyzeTask.ContinueWith((analyzeResult) =>
15             {
16                 Console.WriteLine("Start ReportTask!");
17                 return "Reporting Data:" + analyzeResult.Result;
18             });
19             queryTask.Start();
20             Console.WriteLine(reportTask.Result);
21             Console.WriteLine("主线程结束");
22         }

 除非所有子任务(子任务的子任务)结束运行,否则创建任务(父任务)不会认为已经结束

父任务异常不会影响子任务执行

子任务异常不会影响父任务执行

 1  static void Main(string[] args)
 2         {
 3             Console.WriteLine("主线程开始");
 4             var parentTask = Task.Factory.StartNew(() =>
 5             {
 6                 Console.WriteLine("创建一个父任务");
 7                 var subTask = Task.Factory.StartNew(() =>
 8                 {
 9                     Console.WriteLine("创建一个子任务");
10                     throw new Exception("subTask Error");
11                 }, TaskCreationOptions.AttachedToParent);
12                 Console.WriteLine("父任务结束");
13             });
14             Thread.Sleep(5000);
15             Console.WriteLine("主线程结束");
16         }

 四、异步委托开启多线程

//TODO

部分内容来自:https://www.cnblogs.com/tianqing/p/6970331.html

原文地址:https://www.cnblogs.com/mojiejushi/p/13194341.html