zoukankan      html  css  js  c++  java
  • [C# 线程处理系列]专题五:线程同步——事件构造

    引言:

    其实这部分内容应该是属于专题四,因为这篇也是讲关于线程同步的,但是由于考虑到用户的阅读习惯问题,因为文章太长了,很多人不是很愿意看包括我也是这样的,同时也有和我说可以把代码弄成折叠的,这样就不会太长的,但是我觉得这样也不怎么便于阅读,因为我看别人的博客的时候,看到有代码是折叠起来的时候很多时候不愿意去点,并且点一下之后同样拉长文章的,然后就看到右边的滚动条变小了,本以为快看完了(意思快学到知识了),一看滚动条后发现还有好长的内容很看, 所以就会给人一种不舒服的感觉吧(如果有和我一样的人的话,你肯定懂的是什么感觉的)。所以我把线程同步放到两篇文章里面来说,其实放到两篇文章里面也有一定原因的, 前面讲的线程同步主要是用户模式的(CLR Via C# 一书中是这么定义的,书中说到线程同步分两种:一、用户模式构造 二、内核模式构造,第一次看的时候不是很理解两个名词是什么意思的,我一般理解东西是采用把东西拆分来理解,理解拆分的各个部分后再合起来理解内容的,现在我对着两个的理解是——用户模式构造:对于内核模式构造(指的的是构造操作系内核对象),我们使用类(.net Framework中的类,如 AutoResetEvent, Semaphore类)的方法来实现线程同步,其实内部是调用操作系统的内核对象来实现的线程同步,此时就会导致线程从托管代码到为内核代码,然而用户模式构造,没有调用操作系统内核对象,线程只是在用户的托管代码上执行的),对于用户模式构造和内核模式的构造只是我自己的理解的, 如果有更好的理解方式可以留言告诉下我, 这样我们可以一起讨论和学习了。

    目录:

    一、WaitHandle基类介绍

    二、事件(Event)类实现线程同步

    三、信号量(Semapyore)类实现线程同步

    四、互斥体(Mutex)实现线程同步

    一、WaitHandle基类介绍

    System.Threading命名空间中提供了一个WaitHandle 的抽象基类,此类就是包装了一个Windows内核对象的句柄(句柄可以理解为标示了对象实例的一个数字,具体大家可以查看资料深入理解下的,在这里只是提出理解句柄也是很重要的),在.net Framework中提供了从WaitHandle类中派生的类(我正是用这些派生类在我们的代码中实现线程同步的)。它们的一个继承关系为

    WaitHandle

      EventWaitHandle

           AutoResetEvent

        ManualResetEvent

      Semaphore

      Mutex

    当我们在使用 AutoResetEvent,ManualResetEvent,Semaphore,Mutex这些类的时候,用构造函数来实例化这些类的对象时,其内部都调用了Win32 CreateEvent或CreateEvent函数,或CreateSemaphore或者CreateMutex函数,这些函数调用返回的句柄值都保存在WaitHandle基类定义的SafeWaitHandle字段中。

    二、事件(Event)类实现线程同步

    2.1 AutoResetEvent (自动重置事件)

    先讲讲AutoresetEvent类的构造函数,其定义为:

    public AutoResetEvent(bool initialState);

    构造函数中用一个bool 类型的初始状态来设置AutoResetEvent对象的状态,如果要将AutoResetEvent对象的初始状态设置为终止,则传入bool值为true,若要设置非终止,就传入false。 

    WaitOne方法定义:

    public virtual bool WaitOne(int millisecondsTimeout);该方法用来阻塞线程,当在指定的时间间隔还没有收到一个信号时,将返回false。

    调用Set方法发信号来释放等待线程。在使用过程中WaitOne方法和Set方法都是成对出现的, 一个用于阻塞线程,等待信号,一个用来释放等待线程(就是说调用set方法来发送一个信号,此时WaitOne接受到信号,就释放阻塞的线程,线程就可以继续运行)

    线程通过调用AutoResetEvent的WaitOne方法来等待信号,如果AutoResetEvent对象为非终止状态,则线程被阻止,等到线程调用Set方法来恢复线程执行。如果AutoResetEvent为终止状态时,则线程不会被阻止,此时AutoResetEvent将立即释放线程并返回为非终止状态(指出有线程在使用资源的一种状态)。

    下面通过通过一个例子来演示下AutoResetEvent的使用:

    using System;
    using System.Threading;
    
    namespace KenelMode
    {
        class Program
        {
            // 初始化自动重置事件,并把状态设置为非终止状态
            // 如果这里把初始状态设置为True时,
            // 当调用WaitOne方法时就不会阻塞线程,看到的输出结果的时间就是一样的了
            // 因为设置为True时,表示此时已经为终止状态了。       
            public static AutoResetEvent autoEvent = new AutoResetEvent(false);
            static void Main(string[] args)
            {
                Console.WriteLine("Main Thread Start run at: " +DateTime.Now.ToLongTimeString());
                Thread t = new Thread(TestMethod);
                t.Start();
    
                // 阻塞主线程3秒后
                // 调用 Set方法释放线程,使线程t可以运行
                Thread.Sleep(3000);
    
                // Set 方法就是把事件状态设置为终止状态。
                autoEvent.Set();
                Console.Read();
            }
    
            public static void TestMethod()
            {
                autoEvent.WaitOne();
    
                // 3秒后线程可以运行,所以此时显示的时间应该和主线程显示的时间相差3秒
                Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
            }
        }
    }

    运行结果(从运行结果看确实是过了一秒后在TestMethod方法中的语句):

    上面中用到的是没有带参数的WaitOne方法,该方法表示无限制阻塞线程,直到收到一个事件为止(通过Set方法来发送一个信号),同时我们也可以设置堵塞线程的事件,当超时时,线程将不阻塞直接运行(尽管此时没有通过Set来发送一个信号,线程照样运行,只是WaitOne方法返回的的值不一样)。

    bool WaitOne(int millisecondsTimeout) 收到信号时返回为True,没收到信号返回为false。

    看完下面的代码你可能会形象理解WaitOne(millisecondsTimeout)方法的使用的:

    using System;
    using System.Threading;
    
    namespace KenelMode
    {
        class Program
        {
            // 初始化自动重置事件,并把状态设置为非终止状态
            // 如果这里把初始状态设置为True时,
            // 当调用WaitOne方法时就不会阻塞线程,看到的输出结果的时间就是一样的了
            // 因为设置为True时,表示此时已经为终止状态了。       
            public static AutoResetEvent autoEvent = new AutoResetEvent(false);
            static void Main(string[] args)
            {
                Console.WriteLine("Main Thread Start run at: " +DateTime.Now.ToLongTimeString());
                Thread t = new Thread(TestMethod);
                t.Start();
    
                // 阻塞主线程1秒后
                // 调用 Set方法释放线程,使线程t可以运行
                Thread.Sleep(3000);
    
                // Set 方法就是把事件状态设置为终止状态。
                autoEvent.Set();
                Console.Read();
            }
    
            public static void TestMethod()
            {
                if (autoEvent.WaitOne(2000))
                {
                    Console.WriteLine("Get Singal to Work");
                    // 3秒后线程可以运行,所以此时显示的时间应该和主线程显示的时间相差一秒
                    Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
                }
                else
                {
                    Console.WriteLine("Time Out to work");
                    Console.WriteLine("Method Restart run at: " + DateTime.Now.ToLongTimeString());
                }
            }
        }
    }

    运行结果:

    同时这里可以把Thread.Sleep(3000)改成Thread.Sleep(1000)的时候,就是说AutoResetEvent对象在超时之前就接到信号了, 此时WaitOne(2000)放回的值就是True,就得到的是Get Singal to Work, 之间的事件间隔当然也是1秒了,在这里结果就不贴了。

    2.2 ManualResetEvent(手动重置事件)

    ManualResetEvent的使用和AutoResetEvent的使用很类似,因为他们都是从EventWaitHandle类派生的,不过他们还是有点区别:

    AutoResetEvent 为终止状态时线程调用 WaitOne,则线程不会被阻止。AutoResetEvent 将立即释放线程并返回到非终止状态,当再次调用WaitOne状态时线程会被阻止

    这里请注意如果AutoResetEvent初始为非终止状态时, 调用WaitOne(int millisecondsTimeout)方法后并不会把状态返回为终止状态,此时还是非终止的,调用WaitOne方法自动改变状态只针对初始状态为终止状态时有效。

    然而ManualResetEvent初始状态为终止状态时时调用WaitOne,则线程同样不会被阻止,但是ManualResetEvent的状态不会发生改变(当我再次调用WaitOne方法是一样不会阻止线程),需要我们手动终止()

    下面通过一段代码来说明两者的区别:

    using System;
    using System.Threading;
    
    namespace ManualResetEventSample
    {
        class Program
        {
            // 初始化自动重置事件,并把状态设置为终止状态
            public static AutoResetEvent autoEvent = new AutoResetEvent(true);
    
            ////public static ManualResetEvent autoEvent = new ManualResetEvent(true);
            static void Main(string[] args)
            {
                Console.WriteLine("Main Thread Start run at: " + DateTime.Now.ToLongTimeString());
                Thread t = new Thread(TestMethod);
                t.Start();
                Console.Read();
            }
    
            public static void TestMethod()
            {
                // 初始状态为终止状态,则第一次调用WaitOne方法不会堵塞线程
                // 此时运行的时间间隔应该为0秒,但是因为是AutoResetEvent对象
                // 调用WaitOne方法后立即把状态返回为非终止状态。
                autoEvent.WaitOne();
                Console.WriteLine("Method start at : "+ DateTime.Now.ToLongTimeString());
    
                // 因为此时AutoRestEvent为非终止状态,所以调用WaitOne方法后将阻塞线程1秒,这里设置了超时时间
                // 所以下面语句的和主线程中语句的时间间隔为1秒
                // 当时 ManualResetEvent对象时,因为不会自动重置状态
                // 所以调用完第一次WaitOne方法后状态仍然为非终止状态,所以再次调用不会阻塞线程,所以此时的时间间隔也为0
                // 如果没有设置超时时间的话,下面这行语句将不会执行
                autoEvent.WaitOne(1000);
                Console.WriteLine("Method start at : " + DateTime.Now.ToLongTimeString());
              
               
            }
        }
    }

    运行结果:

    如果你把创建事件为手动重置事件ManualResetEvent时,得到的运行结果就会下面这样:

    2.3 跨进程之间同步

    内核模式的构造可同步在同一台机器上的不同进程中运行的线程,所以我们同样可以使用 AutoResetEvent实现不同进程中运行的线程同步,但是此时需要对AutoResetEvent进行命名,但是AutoResetEvent只提供带一个参数的构造函数的,此时应该如何去实现不同进程中的线程同步的呢?

    其实是有解决办法的,因为AutoResetEvent是继承自EventWaitHandle类的,EventWaitHandle类有多个构造函数的

    除了之前的方法创建AutoResetEvent对象外,

    还可以通过EventWaitHandle AutoEvent = new EventWaitHandle (false, EventResetMode.Auto);这样的方法来构造AutoResetEvent对象,通过

    EventWaitHandle autoEvent = new EventWaitHandle (false, EventResetMode.Auto,"My");方式就可以指定名称了

    下面一段代码演示如何实现跨不同进程中的线程同步:

    using System;
    using System.Threading;
    
    namespace CrossProcess_EventWaitHandle
    {
        class Program
        {
            public static EventWaitHandle autoEvent = new EventWaitHandle(true, EventResetMode.AutoReset, "My");
            static void Main(string[] args)
            {
                Console.WriteLine("Main Thread Start run at: " + DateTime.Now.ToLongTimeString());
                Thread t = new Thread(TestMethod);
                
                // 为了有时间启动另外一个线程
                Thread.Sleep(2000);
                t.Start();
                Console.Read();
            }
    
            public static void TestMethod()
            {
                // 进程一:显示的时间间隔为2秒
                // 进程二中显示的时间间隔为3秒
                // 因为进程二中AutoResetEvent的初始状态为非终止的
                // 因为在进程一中通过WaitOne方法的调用已经把AutoResetEvent的初始状态返回为非终止状态了
                autoEvent.WaitOne(1000);
                Console.WriteLine("Method start at : "+ DateTime.Now.ToLongTimeString());
            }
        }
    }

    运行结果:

    本来打算在一篇文章里面讲述内核模式构造的,写着写着滚动条又变很小了,为了大家的阅读,我把信号量和互斥体放在后面一篇文章里面讲吧,相信后面的内容会很好理解的,因为后面两个类的使用和这篇中讲到的使用很类似,好歹都是继承WaitHandle类的。

    如果您认为这篇文章还不错或者有所收获,您可以通过右边的“打赏”功能 打赏我一杯咖啡【物质支持】,也可以点击右下角的【店长推荐】按钮【精神支持】,因为这两种支持都是我继续写作,分享的最大动力


  • 相关阅读:
    permission 文档 翻译 运行时权限
    TabLayout ViewPager Fragment 简介 案例 MD
    Log 日志工具类 保存到文件 MD
    OkHttp 官方wiki 翻译 MD
    Okhttp 简介 示例 MD
    OkHttp 官方Wiki之【使用案例】
    DialogPlus
    倒计时 总结 Timer Handler CountDownTimer RxJava MD
    RecyclerView 判断滑到底部 顶部 预加载 更多 分页 MD
    CSS3的媒体查询(Media Queries)与移动设备显示尺寸大全
  • 原文地址:https://www.cnblogs.com/zhili/p/Event_Constructor.html
Copyright © 2011-2022 走看看