欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#线程学习笔记五:线程同步--事件构造

程序员文章站 2022-06-20 08:53:50
本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/23/Event_Constructor.html,记录一下学习过程以备后续查用。 前面讲的线程同步主要是用户模式的(CLR Via C# 一书中是这么定义的,书中说到线程同步分两种:一、用户模 ......

    本笔记摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/23/event_constructor.html,记录一下学习过程以备后续查用。

    前面讲的线程同步主要是用户模式的(clr via c# 一书中是这么定义的,书中说到线程同步分两种:一、用户模式构造 二、内核模式构造),对于内核模式构造(指的的是构造操作系内核对象),我们使用.net framework中的类

如autoresetevent、semaphore中方法来实现线程同步,其实其内部是调用操作系统中的内核对象来实现的线程同步,此时就会将线程从托管代码转为内核代码。而用户模式构造,因为没有调用操作系统内核对象,所以线程只会在用

户的托管代码上执行。

    一、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 (自动重置事件)

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

    public autoresetevent(bool initialstate);

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

若要设置为非终止,则传入bool值为false。 

    2.2.2waitone方法定义:

    public virtual bool waitone(int millisecondstimeout);

    该方法用来阻塞线程,当在指定的时间间隔还没有收到一个信号时,将返回false。

    调用set方法发信号来释放等待线程。

    在使用过程中waitone方法和set方法都是成对出现的:

        一个用于阻塞线程,等待信号;

        一个用来释放等待线程(就是说调用set方法来发送一个信号,此时waitone接受到信号,就释放阻塞的线程,线程就可以继续运行。)

    线程通过调用autoresetevent的waitone方法来等待信号,如果autoresetevent对象为非终止状态,则线程被阻止,直到线程调用set方法来恢复线程执行;

如果autoresetevent为终止状态时,则线程不会被阻止,此时autoresetevent将立即释放线程并返回为非终止状态(指出有线程在使用资源的一种状态)。

    下面代码演示autoresetevent的使用:

    class program
    {
        //创建对象
        public static autoresetevent autoresetevent = new autoresetevent(false);

        static void main(string[] args)
        {
            #region 线程同步:autoresetevent的使用
            console.writeline("main thread start run at: " + datetime.now.tolongtimestring());
            thread thread = new thread(waitonemethod);
            thread.start();

            //阻塞主线程3秒钟
            thread.sleep(3000);

            //释放线程
            autoresetevent.set();
            console.read();
            #endregion
        }

        /// <summary>
        /// waitone方法
        /// </summary>
        public static void waitonemethod()
        {
            autoresetevent.waitone();
            console.writeline("method restart run at: " + datetime.now.tolongtimestring());
        }
    }

    运行结果如下:

C#线程学习笔记五:线程同步--事件构造

    若创建对象时,把它改为public static autoresetevent autoresetevent = new autoresetevent(true); ,看到的输出结果的时间就是一样的了。因为设置为true时,表示此时

已经为终止状态了。因此,autoresetevent.set()可以理解为将autoresetevent的状态设置为终止状态,因而释放线程。 

    上面用到的是没带参数的waitone方法,该方法表示无限制阻塞线程,直到收到一个事件为止(通过set方法来发送一个信号)。

    通过bool waitone(int millisecondstimeout),当超时时,线程即使没收到set发来的信号,也将不再阻塞线程而让它继续运行,只是waitone方法返回的值不一样:

    当收到set信号时返回值为true,否则返回值为false。

    下面代码演示waitone(millisecondstimeout)的使用:

    class program
    {
        //创建对象
        public static autoresetevent autoresetevent = new autoresetevent(false);

        static void main(string[] args)
        {
            #region 线程同步:waitone(millisecondstimeout)的使用
            console.writeline("main thread start run at: " + datetime.now.tolongtimestring());
            thread thread = new thread(waitonetimeoutmethod);
            thread.start();

            //阻塞主线程3秒钟
            thread.sleep(3000);

            //释放线程
            autoresetevent.set();
            console.read();
            #endregion
        }

        /// <summary>
        /// waitonetimeout方法
        /// </summary>
        public static void waitonetimeoutmethod()
        {
            if (autoresetevent.waitone(2000))
            {
                console.writeline("get signal to work.");
                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());
            }
        }
    }

    运行结果如下:

C#线程学习笔记五:线程同步--事件构造

    若把thread.sleep(3000);设为thread.sleep(1000);,此时线程将收到set发过来的信号,得到的结果将是get signal to work,时间相差就只有1秒了。

    2.2 manualresetevent(手动重置事件)

    manualresetevent和autoresetevent的使用方法很类似,因为他们都是从eventwaithandle类派生的,不过他们还是有些区别:

    2.2.1autoresetevent 为终止状态(true)时,如果线程调用waitone方法的话线程是不会被阻止的。此时autoresetevent将立即释放线程并返回到非终止状态(false),

在这之后如果线程再次调用waitone方法的话,线程将被阻止。(注:调用waitone方法自动改变状态,仅对初始状态为终止状态时有效。)

    2.2.2manualresetevent为终止状态(true)时,如果线程调用waitone方法的话线程也是不会被阻止的。此manualresetevent将立即释放线程但不会返回到非终止状态(false),

除非我们手动将状态改为终止状态(false),否则在这之后如果线程再次调用waitone方法的话,线程不会被阻止。

    下面代码演示两者的区别:

    class program
    {
        //创建对象
        public static autoresetevent autoresetevent = new autoresetevent(false);
        public static manualresetevent manualresetevent = new manualresetevent(false);

        static void main(string[] args)
        {
            #region 线程同步:manualresetevent的使用
            console.writeline("main thread start run at: " + datetime.now.tolongtimestring());

            thread threadauto = new thread(autoreseteventmethod);
            threadauto.start();
            autoresetevent.set();
            threadauto = new thread(autoreseteventmethod);
            threadauto.start();

            thread threadmanual = new thread(manualreseteventmethod);
            threadmanual.start();
            manualresetevent.set();
            threadmanual = new thread(manualreseteventmethod);
            threadmanual.start();

            console.read();
            #endregion
        }

        /// <summary>
        /// autoresetevent方法
        /// </summary>
        public static void autoreseteventmethod()
        {
            autoresetevent.waitone();
            console.writeline("autoresetevent method restart run at: " + datetime.now.tolongtimestring());
        }

        /// <summary>
        /// manualresetevent方法
        /// </summary>
        public static void manualreseteventmethod()
        {
            manualresetevent.waitone();
            console.writeline("manualresetevent method restart run at: " + datetime.now.tolongtimestring());
        }
    }

    运行结果如下:

C#线程学习笔记五:线程同步--事件构造

    2.3 跨进程之间同步

    内核模式构造可实现同一台机器上的不同进程中的线程进行同步,因此可以使用autoresetevent来实现此功能。此时需要对autoresetevent进行命名,但是autoresetevent只提供了带一个参数的构造函数,该如何实现呢?

办法还是有的,因为autoresetevent是继承自eventwaithandle类,而eventwaithandle类有多个构造函数。

    除了之前的方法创建autoresetevent对象外,还可以通过eventwaithandle autoevent = new eventwaithandle (false, eventresetmode.auto,"my");这样的方式来构造autoresetevent对象,此方式指定了名称。

    下面代码演示跨进程之间的线程同步:

    第一个进程代码:

    class program
    {
        //创建对象
        public static eventwaithandle autoeventfirst = new eventwaithandle(false, eventresetmode.autoreset, "first");
        public static eventwaithandle autoeventsecond = new eventwaithandle(false, eventresetmode.autoreset, "second");

        static void main(string[] args)
        {
            #region 线程同步:跨进程之间的线程同步
            console.writeline("first main thread start run at: " + datetime.now.tolongtimestring());
            thread thread = new thread(eventwaithandlemethod);
            thread.start();

            //为了有时间去启动另外一个进程
            thread.sleep(15000);
            autoeventfirst.set();
            autoeventsecond.set();
            console.read();
            #endregion
        }

        /// <summary>
        /// eventwaithandle方法
        /// </summary>
        public static void eventwaithandlemethod()
        {
            autoeventfirst.waitone();
            console.writeline("first method start at:" + datetime.now.tolongtimestring());
        }
    }

    第二个进程代码:

    class program
    {
        //创建对象
        public static eventwaithandle autoeventsecond = new eventwaithandle(false, eventresetmode.autoreset, "second");
        static void main(string[] args)
        {
            console.writeline("second main thread start run at: " + datetime.now.tolongtimestring());
            thread thread = new thread(eventwaithandlemethod);
            thread.start();
            console.read();
        }

        /// <summary>
        /// eventwaithandle方法
        /// </summary>
        public static void eventwaithandlemethod()
        {
            autoeventsecond.waitone();
            console.writeline("second method start at:" + datetime.now.tolongtimestring());
        }
    }

    运行结果如下:

C#线程学习笔记五:线程同步--事件构造

    从结果可以看出,第一个进程的autoeventsecond.set();信号发出后,第二个进程可以收到并释放线程。