9159金沙游艺场


NET中的对象序列化(转)
图片 4
Bootstrap 4 alpha 发布

C#多线程编程系列(二)- 线程基础

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没有超时返回的功能,导致资源死锁,同步块中的代码永远不会被执行。

图片 1

多线程(基础篇3),多线程基础篇3

  在上一篇多线程(基础篇2)中,我们主要讲述了确定线程的状态、线程优先级、前台线程和后台线程以及向线程传递参数的知识,在这一篇中我们将讲述如何使用C#的lock关键字锁定线程、使用Monitor锁定线程以及线程中的异常处理。

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

1、使用Visual Studio 2015创建一个新的控制台应用程序。

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、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

图片 2

  在第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 2015创建一个新的控制台应用程序。

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、运行该控制台应用程序,运行效果如下图所示:

图片 3

  在上述结果中我们可以看到程序发生了死锁,程序一直结束不了。

  在第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、运行该控制台应用程序,运行效果如下图所示:

图片 4

  此时,我们的控制台应用程序就避免了死锁的发生。

十一、处理异常

   在这一小节中,我们讲述如何在线程中正确地处理异常。正确地将try/catch块放置在线程内部是非常重要的,因为在线程外部捕获线程内部的异常通常是不可能的。

1、使用Visual Studio 2015创建一个新的控制台应用程序。

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、运行该控制台应用程序,运行效果如下图所示:

图片 5

  在第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进程,如下图:

         图片 6

         
进程可以理解为一块包含了某些资源的内存区域,操作系统通过进程这一方式把它的工作划分为不同的单元。一个应用程序可以对应于多个进程。

         
线程是进程中的独立执行单元,对于操作系统而言,它通过调度线程来使应用程序工作,一个进程中至少包含一个线程,我们把该线程成为主线程。线程与进程之间的关系可以理解为:线程是进程的执行单元,操作系统通过调度线程来使应用程序工作;而进程则是线程的容器,它由操作系统创建,又在具体的执行过程中创建了线程。

 

    1.2 线程的调度

       
 在操作系统的书中貌似有提过,“Windows是抢占式多线程操作系统”。之所以这么说它是抢占式的,是因为线程可以在任意时间里被抢占,来调度另一个线程。操作系统为每个线程分配了0-31中的某一级优先级,而且会把优先级高的线程优先分配给CPU执行。

         
Windows支持7个相对线程优先级:Idle、Lowest、BelowNormal、Normal、AboveNormal、Highest和Time-Critical。其中,Normal是默认的线程优先级。程序可以通过设置Thread的Priority属性来改变线程的优先级,该属性的类型为ThreadPriority枚举类型,其成员包括Lowest、BelowNormal、Normal、AboveNormal和Highest。CLR为自己保留了Idle和Time-Critical两个优先级。

 

    1.3 线程也分前后台

         
线程有前台线程和后台线程之分。在一个进程中,当所有前台线程停止运行后,CLR会强制结束所有仍在运行的后台线程,这些后台线程被直接终止,却不会抛出任何异常。主线程将一直是前台线程。我们可以使用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线程的后台线程。由于前台线程执行完毕后CLR会无条件地终止后台线程的运行,所以在前面的代码中,若启动了后台线程,则主线程将会继续运行。主线程执行完后,CLR发现主线程结束,会终止后台线程,然后使整个应用程序结束运行,所以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 }

   
执行结果为:图片 7

 

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

   
前面我们都是通过Thead类来手动创建线程的,然而线程的创建和销毁会耗费大量时间,这样的手动操作将造成性能损失。因此,为了避免因通过Thread手动创建线程而造成的损失,.NET引入了线程池机制。

    2.1 线程池

       
 线程池是指用来存放应用程序中要使用的线程集合,可以将它理解为一个存放线程的地方,这种集中存放的方式有利于对线程进行管理。

       
 CLR初始化时,线程池中是没有线程的。在内部,线程池维护了一个操作请求队列,当应用程序想要执行一个异步操作时,需要调用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 }

结果为:图片 8

 

    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("计数完成");
        }
    }
}

结果为:图片 9

 

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)线程同步每次只允许一个线程访问资源,这会导致线程堵塞。继而系统会创建更多的线程,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 }

   
执行结果:图片 10

    实践出结论。

1、多线程编程必备知识 1.1 进程与线程的概念
当我们打开一个应用程序后,操作系统就会为该…

笔者水平有限,如果错误欢迎各位批评指正!

1.8 前台线程和后台线程

在CLR中,线程要么是前台线程,要么就是后台线程。当一个进程的所有前台线程停止运行时,CLR将强制终止仍在运行的任何后台线程,不会抛出异常。

在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}");
        }
    }
}

运行结果如下图所示。当前台线程10次循环结束以后,创建的后台线程和线程池线程都会被CLR强制结束。

图片 11

目录

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}");
    }
}

运行结果如下图所示,与预期结果一致。

图片 12

C#多线程编程系列(二)- 线程基础


相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图