图片 20

操纵多线程编制程序,三四线程功底篇3

目录

六十一线程(根底篇3卡塔尔国,七十多线程底子篇3

  在上生龙活虎篇八线程(底工篇2卡塔尔国中,我们任重(英文名:rèn zhòng卡塔 尔(英语:State of Qatar)而道远陈说了规定线程的情状、线程优先级、前台线程和后台线程甚至向线程传递参数的学问,在此生龙活虎篇中我们将陈诉如何使用C#的lock关键字锁定线程、使用Monitor锁定线程以至线程中的格外管理。

九、使用C#的lock关键字锁定线程

1、使用Visual Studio 二〇一四创设三个新的调整台应用程序。

2、双击展开“Program.cs”文件,然后改善为如下代码:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 
 5 namespace Recipe09
 6 {
 7     abstract class CounterBase
 8     {
 9         public abstract void Increment();
10         public abstract void Decrement();
11     }
12 
13     class Counter : CounterBase
14     {
15         public int Count { get; private set; }
16 
17         public override void Increment()
18         {
19             Count++;
20         }
21 
22         public override void Decrement()
23         {
24             Count--;
25         }
26     }
27 
28     class CounterWithLock : CounterBase
29     {
30         private readonly object syncRoot = new Object();
31 
32         public int Count { get; private set; }
33 
34         public override void Increment()
35         {
36             lock (syncRoot)
37             {
38                 Count++;
39             }
40         }
41 
42         public override void Decrement()
43         {
44             lock (syncRoot)
45             {
46                 Count--;
47             }
48         }
49     }
50 
51     class Program
52     {
53         static void TestCounter(CounterBase c)
54         {
55             for (int i = 0; i < 100000; i++)
56             {
57                 c.Increment();
58                 c.Decrement();
59             }
60         }
61 
62         static void Main(string[] args)
63         {
64             WriteLine("Incorrect counter");
65             var c1 = new Counter();
66             var t1 = new Thread(() => TestCounter(c1));
67             var t2 = new Thread(() => TestCounter(c1));
68             var t3 = new Thread(() => TestCounter(c1));
69             t1.Start();
70             t2.Start();
71             t3.Start();
72             t1.Join();
73             t2.Join();
74             t3.Join();
75             WriteLine($"Total count: {c1.Count}");
76 
77             WriteLine("--------------------------");
78 
79             WriteLine("Correct counter");
80             var c2 = new CounterWithLock();
81             t1 = new Thread(() => TestCounter(c2));
82             t2 = new Thread(() => TestCounter(c2));
83             t3 = new Thread(() => TestCounter(c2));
84             t1.Start();
85             t2.Start();
86             t3.Start();
87             t1.Join();
88             t2.Join();
89             t3.Join();
90             WriteLine($"Total count: {c2.Count}");
91         }
92     }
93 }

3、运营该调整台应用程序,运转效果(每一趟运维作效果果说不佳两样卡塔尔如下图所示:

图片 1

  在第65行代码处,大家创立了Counter类的一个对象,该类定义了二个粗略的counter变量,该变量可以自增1和自减1。然后在第66~68行代码处,大家创造了八个线程,并动用lambda表明式将Counter对象传递给了“TestCounter”方法,那多个线程分享同一个counter变量,並且对这些变量实行自增和自减操作,那将导致结果的不正确。假使我们一再运作那几个调控台程序,它将打字与印刷出差异的counter值,有不小可能率是0,但超多动静下不是。

  爆发这种状态是因为Counter类是非线程安全的。大家要是第叁个线程在第57行代码处履行达成后,还并未有推行第58行代码时,第一个线程也实践了第57行代码,当时counter的变量值自增了2次,然后,那多少个线程同不经常候举行了第58行处的代码,那会导致counter的变量只自减了1次,由此,产生了不科学的结果。

  为了保险不发生上述不科学的意况,大家亟须确定保障在某三个线程访谈counter变量时,其余全体的线程必需等待其实施落成技艺世襲访问,大家可以动用lock关键字来变成这么些效果。假如我们在有个别线程中锁定叁个对象,其余全部线程必需等到该线程解锁之后手艺访谈到这些指标,因而,可以幸免上述景况的产生。不过要潜心的是,使用这种情势会严重影响程序的性质。更加好的章程我们将会在仙童合营中描述。

十、使用Monitor锁定线程

   在这里一小节中,大家将陈述四个二十四线程编制程序中的何足为奇的多个题材:死锁。大家第少年老成创制一个死锁的演示,然后利用Monitor防止死锁的发出。

1、使用Visual Studio 贰零壹肆成立三个新的调整台应用程序。

2、双击张开“Program.cs”文件,编写代码如下:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe10
 7 {
 8     class Program
 9     {
10         static void LockTooMuch(object lock1, object lock2)
11         {
12             lock (lock1)
13             {
14                 Sleep(1000);
15                 lock (lock2)
16                 {
17                 }
18             }
19         }
20 
21         static void Main(string[] args)
22         {
23             object lock1 = new object();
24             object lock2 = new object();
25 
26             new Thread(() => LockTooMuch(lock1, lock2)).Start();
27 
28             lock (lock2)
29             {
30                 WriteLine("This will be a deadlock!");
31                 Sleep(1000);
32                 lock (lock1)
33                 {
34                     WriteLine("Acquired a protected resource succesfully");
35                 }
36             }
37         }
38     }
39 }

3、运营该调控台应用程序,运维作效果果如下图所示:

图片 2

  在上述结果中大家能够观察程序发生了死锁,程序从来停止不了。

  在第10~19行代码处,大家定义了一个名字为“LockTooMuch”的秘技,在该方法中大家锁定了第三个目的lock1,等待1分钟后,希望锁定第贰个对象lock2。

  在第26行代码处,大家制造了八个新的线程来奉行“LockTooMuch”方法,然后任何时候施行第28行代码。

  在第28~32行代码处,我们在主线程中锁定了对象lock2,然后等待1分钟后,希望锁定第一个指标lock1。

  在创立的新线程中大家锁定了指标lock1,等待1分钟,希望锁定指标lock2,而这时候对象lock2已经被主线程锁定,所以新建线程会等待对象lock2被主线程解锁。不过,在主线程中,我们锁定了目的lock2,等待1分钟,希望锁定指标lock1,而那时候对象lock1已经被创立的线程锁定,所以主线程会等待对象lock1被创立的线程解锁。当发生这种景况的时候,死锁就发生了,所以我们的调节台应用程序最近无法不荒谬停止。

4、要防止死锁的发生,大家得以应用“Monitor.TryEnter”方法来替换lock关键字,“Monitor.TryEnter”方法在乞求不到能源时不会拥塞等待,能够设置超时时间,获取不到直接重回false。校勘代码如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe10
 7 {
 8     class Program
 9     {
10         static void LockTooMuch(object lock1, object lock2)
11         {
12             lock (lock1)
13             {
14                 Sleep(1000);
15                 lock (lock2)
16                 {
17                 }
18             }
19         }
20 
21         static void Main(string[] args)
22         {
23             object lock1 = new object();
24             object lock2 = new object();
25 
26             new Thread(() => LockTooMuch(lock1, lock2)).Start();
27 
28             lock (lock2)
29             {
30                 WriteLine("This will be a deadlock!");
31                 Sleep(1000);
32                 //lock (lock1)
33                 //{
34                 //    WriteLine("Acquired a protected resource succesfully");
35                 //}
36                 if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
37                 {
38                     WriteLine("Acquired a protected resource succesfully");
39                 }
40                 else
41                 {
42                     WriteLine("Timeout acquiring a resource!");
43                 }
44             }
45         }
46     }
47 }

5、运行该调节台应用程序,运转效果如下图所示:

图片 3

  那时候,我们的调整台应用程序就制止了死锁的发生。

十后生可畏、管理极其

   在此一小节中,大家汇报怎么着在线程中国中国科学技术大学学学地管理非常。准确地将try/catch块放置在线程内部是不行主要的,因为在线程外界捕获线程内部的充足平常是不可能的。

1、使用Visual Studio 二零一五成立多个新的调整台应用程序。

2、双击展开“Program.cs”文件,校正代码如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5 
 6 namespace Recipe11
 7 {
 8     class Program
 9     {
10         static void BadFaultyThread()
11         {
12             WriteLine("Starting a faulty thread...");
13             Sleep(TimeSpan.FromSeconds(2));
14             throw new Exception("Boom!");
15         }
16 
17         static void FaultyThread()
18         {
19             try
20             {
21                 WriteLine("Starting a faulty thread...");
22                 Sleep(TimeSpan.FromSeconds(1));
23                 throw new Exception("Boom!");
24             }
25             catch(Exception ex)
26             {
27                 WriteLine($"Exception handled: {ex.Message}");
28             }
29         }
30 
31         static void Main(string[] args)
32         {
33             var t = new Thread(FaultyThread);
34             t.Start();
35             t.Join();
36 
37             try
38             {
39                 t = new Thread(BadFaultyThread);
40                 t.Start();
41             }
42             catch (Exception ex)
43             {
44                 WriteLine(ex.Message);
45                 WriteLine("We won't get here!");
46             }
47         }
48     }
49 }

3、运维该调整台应用程序,运行作效果果如下图所示:

图片 4

  在第10~15行代码处,大家定义了二个名叫“BadFaultyThread”的方法,在该办法中抛出三个十一分,並且未有动用try/catch块捕获该极其。

  在第17~29行代码处,大家定义了叁个名字为“FaultyThread”的措施,在该方法中也抛出二个要命,可是我们应用了try/catch块捕获了该非常。

  在第33~35行代码处,我们创建了八个线程,在该线程中实施了“FaultyThread”方法,我们得以看出在此个新创设的线程中,我们精确地捕获了在“FaultyThread”方法中抛出的不行。

  在第37~46行代码处,我们又新成立了叁个线程,在该线程中奉行了“BadFaultyThread”方法,并且在主线程中运用try/catch块来捕获在新成立的线程中抛出的不胜,不幸的的是我们在主线程中不可能捕获在新线程中抛出的百般。

  因而能够看见,在二个线程中抓获另三个线程中的至极常常是不可行的。

  至此,二十四线程(根底篇卡塔尔大家就汇报到这个时候,之后我们将汇报线程同步相关的学问,敬请期望!

  源码下载

在上风姿浪漫篇多线程(幼功篇2卡塔 尔(阿拉伯语:قطر‎中,我们第生龙活虎描述了明确线程的场馆、线程优先级、前台线程和后台线程以…

您一定要领会的八线程编制程序,通晓八十多线程编制程序

1、四线程编程必备知识

    1.1 进度与线程的概念

       
 当我们开采四个应用程序后,操作系统就能为该应用程序分配一个进度ID,比方展开QQ,你将要职责微电脑的经过选项卡看到QQ.exe进度,如下图:

         图片 5

         
进度可知为一块满含了一些能源的内部存款和储蓄器区域,操作系统通过进程这一艺术把它的劳作划分为不相同的单元。二个应用程序能够对应于多少个经过。

         
线程是进程中的独立试行单元,对于操作系统来讲,它经过调节线程来使应用程序职业,贰个进度中足足含有叁个线程,我们把该线程成为主线程。线程与经过之间的关联得以知道为:线程是进度的实践单元,操作系统通过调整线程来使应用程序专业;而经过则是线程的器皿,它由操作系统创制,又在实际的实施进度中创设了线程。

 

    1.2 线程的调治

       
 在操作系统的书中貌似有提过,“Windows是抢占式十六线程操作系统”。之所以如此说它是抢占式的,是因为线程能够在随机时间里被侵夺,来调解另二个线程。操作系统为各个线程分配了0-3第11中学的某一流优先级,並且会把刚开始阶段级高的线程优分给CPU实施。

         
Windows辅助7个相对线程优先级:Idle、Lowest、BelowNormal、Normal、AboveNormal、Highest和Time-Critical。此中,Normal是暗中认可的线程优先级。程序能够因而安装Thread的Priority属性来改造线程的优先级,该属性的门类为ThreadPriority枚举类型,其成员包涵Lowest、BelowNormal、Normal、AboveNormal和Highest。CLEnclave为温馨保留了Idle和Time-Critical几个优先级。

 

    1.3 线程也分前后台

         
线程有前台线程和后台线程之分。在三个进程中,当有着前台线程甘休运维后,CLSportage会强制停止全体仍在运作的后台线程,这几个后台线程被一向终止,却不会抛出其它至极。主线程将直接是前台线程。大家得以行使Tread类来创制前台线程。

 1 using System;
 2 using System.Threading;
 3 
 4 namespace 多线程1
 5 {
 6     internal class Program
 7     {
 8         private static void Main(string[] args)
 9         {
10             var backThread = new Thread(Worker);
11             backThread.IsBackground = true;
12             backThread.Start();
13             Console.WriteLine("从主线程退出");
14             Console.ReadKey();
15         }
16 
17         private static void Worker()
18         {
19             Thread.Sleep(1000);
20             Console.WriteLine("从后台线程退出");
21         }
22     }
23 }

   
以上代码先经过Thread类成立了三个线程对象,然后通过安装IsBackground属性来指明该线程为后台线程。就算不设置那一个天性,则默以为前台线程。接着调用了Start的办法,那时候后台线程会实施Worker函数的代码。所以在此个程序中有多少个线程,三个是运转Main函数的主线程,叁个是运作Worker线程的后台线程。由于前台线程推行达成后CLCRUISER会无条件地截止后台线程的周转,所以在前边的代码中,若运维了后台线程,则主线程将会继续运维。主线程推行完后,CL兰德奇骏开掘主线程截至,会停下后台线程,然后使一切应用程序甘休运营,所以Worker函数中的Console语句将不会实行。所以地方代码的结果是不会运维Worker函数中的Console语句的。

   
 能够采纳Join函数的艺术,确定保证主线程会在后台线程实践完毕后才起头运行。

 1 using System;
 2 using System.Threading;
 3 
 4 namespace 多线程1
 5 {
 6     internal class Program
 7     {
 8         private static void Main(string[] args)
 9         {
10             var backThread = new Thread(Worker);
11             backThread.IsBackground = true;
12             backThread.Start();
13             backThread.Join();
14             Console.WriteLine("从主线程退出");
15             Console.ReadKey();
16         }
17 
18         private static void Worker()
19         {
20             Thread.Sleep(1000);
21             Console.WriteLine("从后台线程退出");
22         }
23     }
24 }

    以上代码调用Join函数来保管主线程会在后台线程结束后再运转。

    借使您线程试行的章程要求参数,则就要求运用new
Thread的重载构造函数Thread(ParameterizedThreadStart).

 1 using System;
 2 using System.Threading;
 3 
 4 namespace 多线程1
 5 {
 6     internal class Program
 7     {
 8         private static void Main(string[] args)
 9         {
10             var backThread = new Thread(new ParameterizedThreadStart(Worker));
11             backThread.IsBackground = true;
12             backThread.Start("Helius");
13             backThread.Join();
14             Console.WriteLine("从主线程退出");
15             Console.ReadKey();
16         }
17 
18         private static void Worker(object data)
19         {
20             Thread.Sleep(1000);
21             Console.WriteLine($"传入的参数为{data.ToString()}");
22         }
23     }
24 }

   
实行结果为:图片 6

 

2、线程的器皿——线程池

   
后边大家都以因此Thead类来手动创造线程的,可是线程的始建和销毁会损耗大量小时,那样的手动操作将诱致品质损失。由此,为了防止因通过Thread手动创造线程而招致的损失,.NET引进了线程池机制。

    2.1 线程池

       
 线程池是指用来存放应用程序中要动用的线程群集,可以将它驾驭为二个存放线程之处,这种汇集存放的章程有利对线程进行管制。

       
 CLCRUISER初叶化时,线程池中是从未线程的。在里边,线程池维护了二个操作诉求队列,当应用程序想要实施一个异步操作时,需求调用QueueUserWorkItem方法来将相应的职务加多到线程池的伸手队列中。线程池达成的代码会从队列中领到,并将其委派给线程池中的线程去推行。要是线程池未有空余的线程,则线程池也会创设二个新线程去施行提取的天职。而当线程池线程完成有个别义务时,线程不会被销毁,而是再次回到到线程池中,等待响应另一个央浼。由于线程不会被销毁,所以也就防止了品质损失。记住,线程池里的线程都将来台线程,暗中认可等级是Normal。

 

    2.2 通过线程池来得以达成多线程

         
要使用线程池的线程,须要调用静态方法ThreadPool.QueueUserWorkItem,以钦赐线程要调用的主意,该静态方法有七个重载版本:

          public static bool QueueUserWorkItem(WaitCallback callBack);

          public static bool QueueUserWorkItem(WaitCallback
callback,Object state)

         
那八个方法用于向线程池队列增添一个办事先以至三个可选的情事数据。然后,这多个办法就能够立即重返。上边通超过实际例来演示怎么样使用线程池来达成三十二线程编制程序。

 1 using System;
 2 using System.Threading;
 3 
 4 namespace 多线程2
 5 {
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             Console.WriteLine($"主线程ID={Thread.CurrentThread.ManagedThreadId}");
11             ThreadPool.QueueUserWorkItem(CallBackWorkItem);
12             ThreadPool.QueueUserWorkItem(CallBackWorkItem,"work");
13             Thread.Sleep(3000);
14             Console.WriteLine("主线程退出");
15             Console.ReadKey();
16         }
17 
18         private static void CallBackWorkItem(object state)
19         {
20             Console.WriteLine("线程池线程开始执行");
21             if (state != null)
22             {
23                 Console.WriteLine($"线程池线程ID={Thread.CurrentThread.ManagedThreadId},传入的参数为{state.ToString()}");
24             }
25             else
26             {
27                 Console.WriteLine($"线程池线程ID={Thread.CurrentThread.ManagedThreadId}");
28             }
29         }
30     }
31 }

结果为:图片 7

 

    2.3 合作式撤除线程池线程

         .NET
Framework提供了撤回操作的方式,这几个格局是合营式的。为了废除三个操作,必须成立二个System.Threading.CancellationTokenSource对象。下边还是接受代码来演示一下:

using System;
using System.Threading;

namespace 多线程3
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("主线程运行");
            var cts = new CancellationTokenSource();
            ThreadPool.QueueUserWorkItem(Callback, cts.Token);
            Console.WriteLine("按下回车键来取消操作");
            Console.Read();
            cts.Cancel();
            Console.ReadKey();
        }

        private static void Callback(object state)
        {
            var token = (CancellationToken) state;
            Console.WriteLine("开始计数");
            Count(token, 1000);
        }

        private static void Count(CancellationToken token, int count)
        {
            for (var i = 0; i < count; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("计数取消");
                    return;
                }
                Console.WriteLine($"计数为:{i}");
                Thread.Sleep(300);
            }
            Console.WriteLine("计数完成");
        }
    }
}

结果为:图片 8

 

3、线程同步

   
线程同步计数是指四十四线程程序中,为了保证前者线程,唯有翘首以待前面三个线程达成现在技巧继续试行。这就好比活着中排队买票,在头里的人没买到票从前,前边的人总得等待。

    3.1 四十十六线程程序中设有的祸患

         
八十多线程大概相同的时候去做客一个分享能源,那将损坏财富中所保存的数目。这种景况下,只好利用线程同步才干。

    3.2 使用监视器对象完毕线程同步

         
监视器对象(Monitor卡塔尔能够确认保证线程具有对分享能源的排斥访谈权,C#透过lock关键字来提供简化的语法。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace 线程同步
 9 {
10     class Program
11     {
12         private static int tickets = 100;
13         static object globalObj=new object();
14         static void Main(string[] args)
15         {
16             Thread thread1=new Thread(SaleTicketThread1);
17             Thread thread2=new Thread(SaleTicketThread2);
18             thread1.Start();
19             thread2.Start();
20             Console.ReadKey();
21         }
22 
23         private static void SaleTicketThread2()
24         {
25             while (true)
26             {
27                 try
28                 {
29                     Monitor.Enter(globalObj);
30                     Thread.Sleep(1);
31                     if (tickets > 0)
32                     {
33                         Console.WriteLine($"线程2出票:{tickets--}");
34                     }
35                     else
36                     {
37                         break;
38                     }
39                 }
40                 catch (Exception)
41                 {
42                     throw;
43                 }
44                 finally
45                 {
46                     Monitor.Exit(globalObj);
47                 }
48             }
49         }
50 
51         private static void SaleTicketThread1()
52         {
53             while (true)
54             {
55                 try
56                 {
57                     Monitor.Enter(globalObj);
58                     Thread.Sleep(1);
59                     if (tickets > 0)
60                     {
61                         Console.WriteLine($"线程1出票:{tickets--}");
62                     }
63                     else
64                     {
65                         break;
66                     }
67                 }
68                 catch (Exception)
69                 {
70                     throw;
71                 }
72                 finally
73                 {
74                     Monitor.Exit(globalObj);
75                 }
76             }
77         }
78     }
79 }

   
在上述代码中,首先额外定义了多少个静态全局变量globalObj,并将其作为参数字传送递给Enter方法。使用了Monitor锁定的靶子急需为援引类型,而不能够为值类型。因为在将值类型传递给Enter时,它将被先装箱为贰个独立的毒香,之后再传递给Enter方法;而在将变量传递给Exit方法时,也会成立多个独门的援引对象。那时候,传递给Enter方法的目的和传递给Exit方法的指标分裂,Monitor将会抓住SynchronizationLockException非凡。

  

    3.3 线程同步本领存在的标题

       
 (1卡塔尔使用相比繁杂。要用额外的代码把四个线程同时做客的数量包围起来,还并不能够脱漏。

       
 (2卡塔 尔(阿拉伯语:قطر‎使用线程同步会影响程序品质。因为获取和假释同步锁是亟需时间的;何况决定特别线程先得到锁的时候,CPU也要开展和煦。那个额外的干活都会对品质产生影响。

       
 (3卡塔 尔(英语:State of Qatar)线程同步每便只同意三个线程访问财富,这会促成线程堵塞。进而系统会创建更加多的线程,CPU也将要承担更艰辛的调整工作。这些历程会对质量形成影响。

           下边就由代码来解释一下品质的差异:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Diagnostics;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Threading;
 7 using System.Threading.Tasks;
 8 
 9 namespace 线程同步2
10 {
11     class Program
12     {
13         static void Main(string[] args)
14         {
15             int x = 0;
16             const int iterationNumber = 5000000;
17             Stopwatch stopwatch=Stopwatch.StartNew();
18             for (int i = 0; i < iterationNumber; i++)
19             {
20                 x++;
21             }
22             Console.WriteLine($"不使用锁的情况下花费的时间:{stopwatch.ElapsedMilliseconds}ms");
23             stopwatch.Restart();
24             for (int i = 0; i < iterationNumber; i++)
25             {
26                 Interlocked.Increment(ref x);
27             }
28             Console.WriteLine($"使用锁的情况下花费的时间:{stopwatch.ElapsedMilliseconds}ms");
29             Console.ReadKey();
30         }
31     }
32 }

   
实施结果:图片 9

    施行出结论。

1、三十多线程编制程序必备知识 1.1 进度与线程的定义
当我们张开三个应用程序后,操作系统就可以为该…

  • C#八线程编制程序类别(二卡塔尔-
    线程根底

    • 1.1
      简介
    • 1.2
      创设线程
    • 1.3
      暂停线程
    • 1.4
      线程等待
    • 1.5
      终止线程
    • 1.6
      检查测验线程状态
    • 1.7
      线程优先级
    • 1.8
      前台线程和后台线程
    • 1.9
      向线程传递参数
    • 1.10 C#
      Lock关键字的运用
    • 1.11
      使用Monitor类锁定财富
    • 1.12
      三八线程中管理特别
  • 参照他事他说加以侦察书籍
  • 作者水平有限,借使不当款待各位钻探指正!

C#多线程编制程序类别(二卡塔尔- 线程根基


1.1 简介

线程幼功首要不外乎线程创造、挂起、等待和结束线程。关于越来越多的线程的最底层实现,CPU时间片轮转等等的文化,可以参照他事他说加以考察《深入理解计算机系统》黄金时代书中关于进度和线程的章节,本文不过多废话。

1.2 创制线程

在C#语言中,创立线程是生龙活虎件极度轻松的作业;它只须要用到
System.Threading取名空间,个中重视使用Thread类来创立线程。

示范代码如下所示:

using System;
using System.Threading; // 创建线程需要用到的命名空间
namespace Recipe1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1.创建一个线程 PrintNumbers为该线程所需要执行的方法
            Thread t = new Thread(PrintNumbers);
            // 2.启动线程
            t.Start();

            // 主线程也运行PrintNumbers方法,方便对照
            PrintNumbers();
            // 暂停一下
            Console.ReadKey();
        }

        static void PrintNumbers()
        {
            // 使用Thread.CurrentThread.ManagedThreadId 可以获取当前运行线程的唯一标识,通过它来区别线程
            Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印...");
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i}");
            }
        }
    }
}

运转结果如下图所示,大家能够通过运转结果查出上面的代码创造了一个线程,然后主线程和创设的线程交叉输出结果,那注脚PrintNumbers办法同一时候运维在主线程和其它一个线程中。

图片 10

1.3 暂停线程

停顿线程这里运用的法子是透过Thread.Sleep主意,假使线程实施Thread.Sleep方法,那么操作系统就要内定的年华内不为该线程分配任几时间片。借使Sleep时间100ms那么操作系统将起码让该线程睡眠100ms可能更加长日子,所以Thread.Sleep方法不能用作高精度的机械漏刻使用。

示范代码如下所示:

using System;
using System.Threading; // 创建线程需要用到的命名空间
namespace Recipe2
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1.创建一个线程 PrintNumbers为该线程所需要执行的方法
            Thread t = new Thread(PrintNumbersWithDelay);
            // 2.启动线程
            t.Start();

            // 暂停一下
            Console.ReadKey();
        }

        static void PrintNumbersWithDelay()
        {
            Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
            for (int i = 0; i < 10; i++)
            {
                //3. 使用Thread.Sleep方法来使当前线程睡眠,TimeSpan.FromSeconds(2)表示时间为 2秒
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
            }
        }
    }
}

运维结果如下图所示,通过下图能够鲜明上边的代码是有效的,通过Thread.Sleep形式,使线程休眠了2秒左右,可是并非专程确切的2秒。验证了位置的传教,它的睡觉是最少让线程睡眠多长期,实际不是早晚多久。

图片 11

1.4 线程等待

在本章中,线程等待使用的是Join方法,该方法将中断实施当前线程,直到所等待的另叁个线程终止。在大概的线程同步中会使用到,但它比较容易,不作过多介绍。

演示代码如下所示:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"-------开始执行 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");

        // 1.创建一个线程 PrintNumbersWithDelay为该线程所需要执行的方法
        Thread t = new Thread(PrintNumbersWithDelay);
        // 2.启动线程
        t.Start();
        // 3.等待线程结束
        t.Join();

        Console.WriteLine($"-------执行完毕 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");
        // 暂停一下
        Console.ReadKey();
    }

    static void PrintNumbersWithDelay()
    {
        Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
        }
    }
}

运作结果如下图所示,最早试行和实行完结两条消息由主线程打印;根据其出口的生龙活虎生机勃勃可知主线程是等待此外的线程甘休后才输出推行达成那条音讯。

图片 12

1.5 终止线程

悬停线程使用的格局是Abort办法,当该办法被施行时,将尝试销毁该线程。通过吸引ThreadAbortException极度使线程被死灭。但平常不引入应用该格局,原因有以下几点。

  1. 使用Abort艺术只是尝试行销售毁该线程,但不鲜明能止住线程。
  2. 假使被截至的线程在推行lock内的代码,那么终止线程会形成线程不安全。
  3. 线程终止时,CLPAJERO会保险自身之中的数据结构不会破坏,不过BCL无法担保。

基于以上原因不引入应用Abort办法,在实际项目中日常选用CancellationToken来终止线程。

演示代码如下所示:

static void Main(string[] args)
{
    Console.WriteLine($"-------开始执行 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");

    // 1.创建一个线程 PrintNumbersWithDelay为该线程所需要执行的方法
    Thread t = new Thread(PrintNumbersWithDelay);
    // 2.启动线程
    t.Start();
    // 3.主线程休眠6秒
    Thread.Sleep(TimeSpan.FromSeconds(6));
    // 4.终止线程
    t.Abort();

    Console.WriteLine($"-------执行完毕 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");
    // 暂停一下
    Console.ReadKey();
}

static void PrintNumbersWithDelay()
{
    Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(TimeSpan.FromSeconds(2));
        Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
    }
}

运作结果如下图所示,运行所创建的线程3后,6分钟主线程调用了Abort方法,线程3并未有继续实施便甘休了;与预期的结果风姿罗曼蒂克律。

图片 13

1.6 检查实验线程状态

线程的图景可透过访谈ThreadState属性来检测,ThreadState是一个枚举类型,后生可畏共有10种情状,状态具体意思如下表所示。

成员名称 说明
Aborted 线程处于 Stopped 状态中。
AbortRequested 已对线程调用了 Thread.Abort 方法,但线程尚未收到试图终止它的挂起的 System.Threading.ThreadAbortException
Background 线程正作为后台线程执行(相对于前台线程而言)。此状态可以通过设置 Thread.IsBackground 属性来控制。
Running 线程已启动,它未被阻塞,并且没有挂起的 ThreadAbortException
Stopped 线程已停止。
StopRequested 正在请求线程停止。这仅用于内部。
Suspended 线程已挂起。
SuspendRequested 正在请求线程挂起。
Unstarted 尚未对线程调用 Thread.Start 方法。
WaitSleepJoin 由于调用 WaitSleepJoin,线程已被阻止。

下表列出导致敬况改正的操作。

操作 ThreadState
在公共语言运行库中创建线程。 Unstarted
线程调用 Start Unstarted
线程开始运行。 Running
线程调用 Sleep WaitSleepJoin
线程对其他对象调用 Wait WaitSleepJoin
线程对其他线程调用 Join WaitSleepJoin
另一个线程调用 Interrupt Running
另一个线程调用 Suspend SuspendRequested
线程响应 Suspend 请求。 Suspended
另一个线程调用 Resume Running
另一个线程调用 Abort AbortRequested
线程响应 Abort 请求。 Stopped
线程被终止。 Stopped

躬体力行代码如下所示:

static void Main(string[] args)
{
    Console.WriteLine("开始执行...");

    Thread t = new Thread(PrintNumbersWithStatus);
    Thread t2 = new Thread(DoNothing);

    // 使用ThreadState查看线程状态 此时线程未启动,应为Unstarted
    Console.WriteLine($"Check 1 :{t.ThreadState}");

    t2.Start();
    t.Start();

    // 线程启动, 状态应为 Running
    Console.WriteLine($"Check 2 :{t.ThreadState}");

    // 由于PrintNumberWithStatus方法开始执行,状态为Running
    // 但是经接着会执行Thread.Sleep方法 状态会转为 WaitSleepJoin
    for (int i = 1; i < 30; i++)
    {
        Console.WriteLine($"Check 3 : {t.ThreadState}");
    }

    // 延时一段时间,方便查看状态
    Thread.Sleep(TimeSpan.FromSeconds(6));

    // 终止线程
    t.Abort();

    Console.WriteLine("t线程被终止");

    // 由于该线程是被Abort方法终止 所以状态为 Aborted或AbortRequested
    Console.WriteLine($"Check 4 : {t.ThreadState}");
    // 该线程正常执行结束 所以状态为Stopped
    Console.WriteLine($"Check 5 : {t2.ThreadState}");

    Console.ReadKey();
}

static void DoNothing()
{
    Thread.Sleep(TimeSpan.FromSeconds(2));
}

static void PrintNumbersWithStatus()
{
    Console.WriteLine("t线程开始执行...");

    // 在线程内部,可通过Thread.CurrentThread拿到当前线程Thread对象
    Console.WriteLine($"Check 6 : {Thread.CurrentThread.ThreadState}");
    for (int i = 1; i < 10; i++)
    {
        Thread.Sleep(TimeSpan.FromSeconds(2));
        Console.WriteLine($"t线程输出 :{i}");
    }
}

运维结果如下图所示,与预期的结果后生可畏致。

图片 14

1.7 线程优先级

Windows操作系统为抢占式四线程(Preemptive
multithreaded)操作系统,是因为线程可在其余时间甘休(被枪占卡塔尔国并调治另一个线程。

Windows操作系统中线程有0(最低) ~ 31(最高)的优先级,而优先级越高所能占用的CPU时间就越来越多,明确有些线程所处的事先级须要思谋进程优先级对峙线程优先级几个优先级。

  1. 进程优先级:Windows扶持6个进程优先级,分别是Idle、Below Normal、Normal、Above normal、High 和Realtime。默认为Normal
  2. 相持线程优先级:相对线程优先级是对峙于经过优先级的,因为经过包涵了线程。Windows辅助7个绝对线程优先级,分别是Idle、Lowest、Below Normal、Normal、Above Normal、Highest 和 Time-Critical.默认为Normal

下表总括了进度的预先级线程的相对优先级优先级(0~31)的照耀关系。粗体为相对线程优先级,斜体为经过优先级

Idle Below Normal Normal Above Normal High Realtime
Time-Critical 15 15 15 15 15 31
Highest 6 8 10 12 15 26
Above Normal 5 7 9 11 14 25
Normal 4 6 8 10 13 24
Below Normal 3 5 7 9 12 23
Lowest 2 4 6 8 11 22
Idle 1 1 1 1 1 16

而在C#程序中,可改良线程的相对优先级,须要安装ThreadPriority个性,可设置为ThreadPriority枚举类型的六个值之风姿罗曼蒂克:Lowest、BelowNormal、Normal、AboveNormal 或 Highest。CLPRADO为和谐保留了IdleTime-Critical优先级,程序中不可设置。

事必躬亲代码如下所示。

static void Main(string[] args)
{
    Console.WriteLine($"当前线程优先级: {Thread.CurrentThread.Priority} rn");

    // 第一次测试,在所有核心上运行
    Console.WriteLine("运行在所有空闲的核心上");
    RunThreads();
    Thread.Sleep(TimeSpan.FromSeconds(2));

    // 第二次测试,在单个核心上运行
    Console.WriteLine("rn运行在单个核心上");
    // 设置在单个核心上运行
    System.Diagnostics.Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
    RunThreads();

    Console.ReadLine();
}

static void RunThreads()
{
    var sample = new ThreadSample();

    var threadOne = new Thread(sample.CountNumbers);
    threadOne.Name = "线程一";
    var threadTwo = new Thread(sample.CountNumbers);
    threadTwo.Name = "线程二";

    // 设置优先级和启动线程
    threadOne.Priority = ThreadPriority.Highest;
    threadTwo.Priority = ThreadPriority.Lowest;
    threadOne.Start();
    threadTwo.Start();

    // 延时2秒 查看结果
    Thread.Sleep(TimeSpan.FromSeconds(2));
    sample.Stop();
}

class ThreadSample
{
    private bool _isStopped = false;

    public void Stop()
    {
        _isStopped = true;
    }

    public void CountNumbers()
    {
        long counter = 0;

        while (!_isStopped)
        {
            counter++;
        }

        Console.WriteLine($"{Thread.CurrentThread.Name} 优先级为 {Thread.CurrentThread.Priority,11} 计数为 = {counter,13:N0}");
    }
}

运营结果如下图所示。Highest占用的CPU时间明显多于Lowest。当程序运维在拥有大旨上时,线程可以在分歧宗旨同有的时候候运维,所以HighestLowest差别会小部分。

图片 15

1.8 前台线程和后台线程

在CL奥迪Q5中,线程要么是前台线程,要么便是后台线程。当一个经过的装有前台线程停止运转时,CL冠道将强制甘休仍在运维的别的后台线程,不会抛出极度。

在C#中可透过Thread类中的IsBackground品质来钦定是不是为后台线程。在线程生命周期中,任何时候都可在这里早先台线程变为后台线程。线程池中的线程暗中认可为后台线程

演示代码如下所示。

static void Main(string[] args)
{
    var sampleForeground = new ThreadSample(10);
    var sampleBackground = new ThreadSample(20);
    var threadPoolBackground = new ThreadSample(20);

    // 默认创建为前台线程
    var threadOne = new Thread(sampleForeground.CountNumbers);
    threadOne.Name = "前台线程";

    var threadTwo = new Thread(sampleBackground.CountNumbers);
    threadTwo.Name = "后台线程";
    // 设置IsBackground属性为 true 表示后台线程
    threadTwo.IsBackground = true;

    // 线程池内的线程默认为 后台线程
    ThreadPool.QueueUserWorkItem((obj) => {
        Thread.CurrentThread.Name = "线程池线程";
        threadPoolBackground.CountNumbers();
    });

    // 启动线程 
    threadOne.Start();
    threadTwo.Start();
}

class ThreadSample
{
    private readonly int _iterations;

    public ThreadSample(int iterations)
    {
        _iterations = iterations;
    }
    public void CountNumbers()
    {
        for (int i = 0; i < _iterations; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(0.5));
            Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
        }
    }
}

运作结果如下图所示。当前台线程13遍巡回甘休之后,成立的后台线程和线程池线程都会被CLSportage强制结束。

图片 16

1.9 向线程传递参数

向线程中传递参数常用的有两种方法,构造函数字传送值、Start方法传值和Lambda表明式传值,通常常用Start方法来传值。

示范代码如下所示,通过三种艺术来传递参数,告诉线程中的循环最后供给循环两次。

static void Main(string[] args)
{
    // 第一种方法 通过构造函数传值
    var sample = new ThreadSample(10);

    var threadOne = new Thread(sample.CountNumbers);
    threadOne.Name = "ThreadOne";
    threadOne.Start();
    threadOne.Join();

    Console.WriteLine("--------------------------");

    // 第二种方法 使用Start方法传值 
    // Count方法 接收一个Object类型参数
    var threadTwo = new Thread(Count);
    threadTwo.Name = "ThreadTwo";
    // Start方法中传入的值 会传递到 Count方法 Object参数上
    threadTwo.Start(8);
    threadTwo.Join();

    Console.WriteLine("--------------------------");

    // 第三种方法 Lambda表达式传值
    // 实际上是构建了一个匿名函数 通过函数闭包来传值
    var threadThree = new Thread(() => CountNumbers(12));
    threadThree.Name = "ThreadThree";
    threadThree.Start();
    threadThree.Join();
    Console.WriteLine("--------------------------");

    // Lambda表达式传值 会共享变量值
    int i = 10;
    var threadFour = new Thread(() => PrintNumber(i));
    i = 20;
    var threadFive = new Thread(() => PrintNumber(i));
    threadFour.Start();
    threadFive.Start();
}

static void Count(object iterations)
{
    CountNumbers((int)iterations);
}

static void CountNumbers(int iterations)
{
    for (int i = 1; i <= iterations; i++)
    {
        Thread.Sleep(TimeSpan.FromSeconds(0.5));
        Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
    }
}

static void PrintNumber(int number)
{
    Console.WriteLine(number);
}

class ThreadSample
{
    private readonly int _iterations;

    public ThreadSample(int iterations)
    {
        _iterations = iterations;
    }
    public void CountNumbers()
    {
        for (int i = 1; i <= _iterations; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(0.5));
            Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
        }
    }
}

运维结果如下图所示,与预期结果符合。

图片 17

1.10 C# Lock关键字的应用

在三十二线程的类别中,由于CPU的时间片轮转等线程调解算法的运用,轻巧并发线程安全难题。具体可参照《深入理解计算机系统》风姿洒脱书相关的章节。

在C#中lock重要字是八个语法糖,它将Monitor装进,给object加上一个互斥锁,进而实今世码的线程安全,Monitor会在下黄金年代节中牵线。

对于lock一言九鼎字照旧Monitor锁定的靶子,都不得不小心接受,不适于的抉择或许会以致悲惨的性问责题以致发出死锁。以下有几条有关选拔锁定指标的提议。

  1. 一齐锁定的目的不能够是值类型。因为使用值类型时会有装箱的难点,装箱后的就成了二个新的实例,会引致Monitor.Enter()Monitor.Exit()收下到分化的实例而错失关联性
  2. 防止锁定this、typeof(type)和stringthistypeof(type)锁定恐怕在此外不相干的代码中会有相符的概念,导致多少个联合块互相窒碍。string亟需考虑字符串拘留的标题,纵然同八个字符串常量在多个地点现身,或者引用的会是同三个实例。
  3. 对象的抉择成效域尽可能适逢其会到达必要,使用静态的、私有的变量。

以下演示代码完结了二十四线程景况下的计数功效,风度翩翩种完成是线程不安全的,会招致结果与预期不切合,但也可以有希望准确。其它生龙活虎种采用了lock重在字张开线程同步,所以它结果是无庸置疑的。

static void Main(string[] args)
{
    Console.WriteLine("错误的多线程计数方式");

    var c = new Counter();
    // 开启3个线程,使用没有同步块的计数方式对其进行计数
    var t1 = new Thread(() => TestCounter(c));
    var t2 = new Thread(() => TestCounter(c));
    var t3 = new Thread(() => TestCounter(c));
    t1.Start();
    t2.Start();
    t3.Start();
    t1.Join();
    t2.Join();
    t3.Join();

    // 因为多线程 线程抢占等原因 其结果是不一定的  碰巧可能为0
    Console.WriteLine($"Total count: {c.Count}");
    Console.WriteLine("--------------------------");

    Console.WriteLine("正确的多线程计数方式");

    var c1 = new CounterWithLock();
    // 开启3个线程,使用带有lock同步块的方式对其进行计数
    t1 = new Thread(() => TestCounter(c1));
    t2 = new Thread(() => TestCounter(c1));
    t3 = new Thread(() => TestCounter(c1));
    t1.Start();
    t2.Start();
    t3.Start();
    t1.Join();
    t2.Join();
    t3.Join();

    // 其结果是一定的 为0
    Console.WriteLine($"Total count: {c1.Count}");

    Console.ReadLine();
}

static void TestCounter(CounterBase c)
{
    for (int i = 0; i < 100000; i++)
    {
        c.Increment();
        c.Decrement();
    }
}

// 线程不安全的计数
class Counter : CounterBase
{
    public int Count { get; private set; }

    public override void Increment()
    {
        Count++;
    }

    public override void Decrement()
    {
        Count--;
    }
}

// 线程安全的计数
class CounterWithLock : CounterBase
{
    private readonly object _syncRoot = new Object();

    public int Count { get; private set; }

    public override void Increment()
    {
        // 使用Lock关键字 锁定私有变量
        lock (_syncRoot)
        {
            // 同步块
            Count++;
        }
    }

    public override void Decrement()
    {
        lock (_syncRoot)
        {
            Count--;
        }
    }
}

abstract class CounterBase
{
    public abstract void Increment();

    public abstract void Decrement();
}

运维结果如下图所示,与预期结果相符。

图片 18

1.11 使用Monitor类锁定财富

Monitor类入眼用于线程同步中,
lock根本字是对Monitor类的一个包装,其卷入结构如下代码所示。

try
{
    Monitor.Enter(obj);
    dosomething();
}
catch(Exception ex)
{  
}
finally
{
    Monitor.Exit(obj);
}

以下代码演示了动用Monitor.TyeEnter()方法制止财富死锁和使用lock发出能源死锁的景色。

        static void Main(string[] args)
        {
            object lock1 = new object();
            object lock2 = new object();

            new Thread(() => LockTooMuch(lock1, lock2)).Start();

            lock (lock2)
            {
                Thread.Sleep(1000);
                Console.WriteLine("Monitor.TryEnter可以不被阻塞, 在超过指定时间后返回false");
                // 如果5S不能进入同步块,那么返回。
                // 因为前面的lock锁定了 lock2变量  而LockTooMuch()一开始锁定了lock1 所以这个同步块无法获取 lock1 而LockTooMuch方法内也不能获取lock2
                // 只能等待TryEnter超时 释放 lock2 LockTooMuch()才会是释放 lock1
                if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
                {
                    Console.WriteLine("获取保护资源成功");
                }
                else
                {
                    Console.WriteLine("获取资源超时");
                }
            }

            new Thread(() => LockTooMuch(lock1, lock2)).Start();

            Console.WriteLine("----------------------------------");
            lock (lock2)
            {
                Console.WriteLine("这里会发生资源死锁");
                Thread.Sleep(1000);
                // 这里必然会发生死锁  
                // 本同步块 锁定了 lock2 无法得到 lock1
                // 而 LockTooMuch 锁定了 lock1 无法得到 lock2
                lock (lock1)
                {
                    // 该语句永远都不会执行
                    Console.WriteLine("获取保护资源成功");
                }
            }
        }

        static void LockTooMuch(object lock1, object lock2)
        {
            lock (lock1)
            {
                Thread.Sleep(1000);
                lock (lock2) ;
            }
        }

运作结果如下图所示,因为运用Monitor.TryEnter()主题在逾期今后会重临,不会拥塞线程,所以并未有产生死锁。而第二段代码中lock未曾过期重临的意义,招致能源死锁,同步块中的代码永久不会被实施。

图片 19

1.12 四线程中管理特别

在八线程中拍卖特别应当利用前后原则,在哪些线程产生相当那么所在的代码块料定要有对应的那些管理。不然也许会造成程序崩溃、数据错过。

主线程中接收try/catch言语是无法捕获制造线程中的格外。但是万后生可畏碰着不可预料的特别,可由此监听AppDomain.CurrentDomain.UnhandledException事件来开展捕获和丰裕管理。

示范代码如下所示,格外管理 1 和 卓殊处理 2 能健康被奉行,而非常管理 3
是不行的。

static void Main(string[] args)
{
    // 启动线程,线程代码中进行异常处理
    var t = new Thread(FaultyThread);
    t.Start();
    t.Join();

    // 捕获全局异常
    AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    t = new Thread(BadFaultyThread);
    t.Start();
    t.Join();

    // 线程代码中不进行异常处理,尝试在主线程中捕获
    AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
    try
    {
        t = new Thread(BadFaultyThread);
        t.Start();
    }
    catch (Exception ex)
    {
        // 永远不会运行
        Console.WriteLine($"异常处理 3 : {ex.Message}");
    }
}

private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    Console.WriteLine($"异常处理 2 :{(e.ExceptionObject as Exception).Message}");
}

static void BadFaultyThread()
{
    Console.WriteLine("有异常的线程已启动...");
    Thread.Sleep(TimeSpan.FromSeconds(2));
    throw new Exception("Boom!");
}

static void FaultyThread()
{
    try
    {
        Console.WriteLine("有异常的线程已启动...");
        Thread.Sleep(TimeSpan.FromSeconds(1));
        throw new Exception("Boom!");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"异常处理 1 : {ex.Message}");
    }
}

运作结果如下图所示,与预期结果相仿。

图片 20

参考书籍

本文主要参照了以下几本书,在这里对那几个小编表示真心的感激你们提供了那样好的材料。

  1. 《CLR via C#》
  2. 《C# in Depth Third Edition》
  3. 《Essential C# 6.0》
  4. 《Multithreading with C# Cookbook Second Edition》

线程底工那生机勃勃章节好不轻易收拾完了,是小编学习进程中的笔记和思虑。安顿依照《Multithreading
with C# Cookbook Second
Edition》那本书的构造,后生可畏共更新10个章节,先立个Flag。


源码下载点击链接
演示源码下载

小编水平有限,如若不当款待各位商量指正!

发表评论