当前位置:首页 > C#教程 > C#高级

C#线程同步--线程通信

问题抽象:当某个操作的执行必须依赖于另一个操作的完成时,需要有个机制来保证这种先后关系。
线程通信方案:manualreseteventslim、manualresetevent、autoresetevent
方案特性:提供线程通知的能力,没有接到通知前,线程必须等待,有先后顺序。

1、manualresetevent类
     对象有两种信号量状态true和false。构造函数设置初始状态。简单来说,
     ◆ 如果构造函数由true创建,则第一次waitone()不会阻止线程的执行,而是等待reset后的第二次waitone()才阻止线程执行。
     ◆ 如果构造函数有false创建,则waitone()必须等待set()才能往下执行。
  一句话总结就是:是否忽略第一次阻塞。
  方法如下:
       ◆ waitone:该方法用于阻塞线程,默认是无限期的阻塞,支持设置等待时间,如果超时就放弃阻塞,不等了,继续往下执行;
       ◆ set:手动修改信号量为true,也就是恢复线程执行;

       ◆ reset:重置状态; 重置后,又能waitone()啦

  using system;

using system.threading;

 

namespace consoleapp1

{

    class program

    {

        //一开始设置为false才会等待收到信号才执行

        static manualresetevent mr = new manualresetevent(false);

        public static void main()

        {

            thread t = new thread(run);

            //启动辅助线程

            t.start();

            //等待辅助线程执行完毕之后,主线程才继续执行

            console.writeline("主线程一边做自己的事,一边等辅助线程执行!" + datetime.now.tostring("mm:ss"));

            mr.waitone();

            console.writeline("收到信号,主线程继续执行" + datetime.now.tostring("mm:ss"));

            console.readkey();

        }

 

        static void run()

        {

            //模拟长时间任务

            thread.sleep(3000);

            console.writeline("辅助线程长时间任务完成!" + datetime.now.tostring("mm:ss"));

            mr.set();

        }

    }

}

 

 

        在思维上,这个东西可以有两种用法,一种是让主线程等待辅助线程,一种是辅助线程等待主线程。

  但无论怎么用,都是让一个线程等待或唤醒另外一个线程。

  reset方法调用示例

using system;

using system.threading;

namespace consoleapp1

{

    class program

    {

        //一开始设置为false,当遇到waitone()时,需要set()才能继续执行

        static manualresetevent mr = new manualresetevent(false);

        public static void main()

        {

            thread t = new thread(run);

            console.writeline("开始" + datetime.now.tostring("mm:ss"));

            t.start();

            mr.waitone();

            console.writeline("第一次等待完成!" + datetime.now.tostring("mm:ss"));

            mr.reset();     //重置后,又能waitone()啦

            mr.waitone(3000);

            console.writeline("第二次等待完成!" + datetime.now.tostring("mm:ss"));

            console.readkey();

        }

        static void run()

        {

            mr.set();

            thread.sleep(2000);

            mr.set();

        }

    }

}

如果以上代码不使用reset,则直接输出第二次等待完成,而不会等待2秒。

2、autoresetevent类
  autoresetevent与manualresetevent的区别在于autoresetevent 的waitone会改变信号量的值为false,让其等待阻塞。
  比如说初始信号量为true,如果waitone超时信号量将自动变为false,而manualresetevent则不会。
  第二个区别:
  ◆ manualresetevent:每次可以唤醒一个或多个线程
  ◆ autoresetevent:每次只能唤醒一个线程

    using system;

using system.threading;

namespace consoleapp1

{

    class program

    {

        static autoresetevent ar = new autoresetevent(true);

        public static void main()

        {

            thread t = new thread(run);

            t.start();

            bool state = ar.waitone(1000);

            console.writeline("当前的信号量状态:{0}", state);

            state = ar.waitone(1000);

            console.writeline("再次waitone后现在的状态是:{0}", state);

            state = ar.waitone(1000);

            console.writeline("再次waitone后现在的状态是:{0}", state);

            console.readkey();

        }

        static void run()

        {

            console.writeline("当前时间" + datetime.now.tostring("mm:ss"));

        }

    }

}

  autoresetevent 允许线程通过发信号互相通信。通常,此通信涉及线程需要独占访问的资源。
  线程通过调用 autoresetevent 上的 waitone 来等待信号。如果 autoresetevent 处于非终止状态,则该线程阻塞,并等待当前控制资源的线程,通过调用 set 发出资源可用的信号。调用 set 向 autoresetevent 发信号以释放等待线程。autoresetevent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。如果没有任何线程在等待,则状态将无限期地保持为终止状态。可以通过将一个布尔值传递给构造函数来控制 autoresetevent 的初始状态,如果初始状态为终止状态,则为 true;否则为 false。
  通俗的来讲只有等myreseteven.set()成功运行后,myreseteven.waitone()才能够获得运行机会;set是发信号,waitone是等待信号,只有发了信号,等待的才会执行。如果不发的话,waitone后面的程序就永远不会执行。下面我们来举一个例子:我去书店买书,当我选中一本书后我会去收费处付钱,
付好钱后再去仓库取书。这个顺序不能颠倒,我作为主线程,收费处和仓库做两个辅助线程,代码如下:

using system;

using system.threading;

namespace consoleapp1

{

    class testautoreseevent

    {

        static autoresetevent buybookevent = new autoresetevent(false);

        static autoresetevent paymoneyevent = new autoresetevent(false);

        static autoresetevent getbookevent = new autoresetevent(false);

        static int number = 10;

        public static void run()

        {

            thread buybookthread = new thread(new threadstart(buybookproc));

            buybookthread.name = "买书线程";

            thread paymoneythread = new thread(new threadstart(paymoneyproc));

            paymoneythread.name = "付钱线程";

            thread getbookthread = new thread(new threadstart(getbookproc));

            getbookthread.name = "取书线程";

            buybookthread.start();

            paymoneythread.start();

            getbookthread.start();

            buybookthread.join();

            paymoneythread.join();

            getbookthread.join();

        }

        static void buybookproc()

        {

            while (number > 0)

            {

                console.writeline("{0}:数量{1}", thread.currentthread.name, number);

                paymoneyevent.set();

                buybookevent.waitone();

                console.writeline("------------------------------------------");

                number--;

            }

        }

        static void paymoneyproc()

        {

            while (number > 0)

            {

                paymoneyevent.waitone();

                console.writeline("{0}:数量{1}", thread.currentthread.name, number);

                getbookevent.set();

            }

        }

        static void getbookproc()

        {

            while (number > 0)

            {

                getbookevent.waitone();

                console.writeline("{0}:数量{1}", thread.currentthread.name, number);

                buybookevent.set();

            }

        }

    }

}

namespace consoleapp1

{

    class program

    {

        public static void main()

        {

            testautoreseevent.run();

        }

    }

}

3、manualreseteventslim类

  manualreseteventslim是manualresetevent的混合版本,一直保持大门敞开直到手工调用reset方法,
  set() 相当于打开了大门从而允许准备好的线程接收信号并继续工作
  reset() 相当于关闭了大门 此时已经准备好执行的信号量 则只能等到下次大门开启时才能够执行

using system;

using system.threading;

namespace consoleapp1

{

    class program

    {

        static void main(string[] args)

        {

            var t1 = new thread(() => travelthroughgates("thread 1", 5));

            var t2 = new thread(() => travelthroughgates("thread 2", 6));

            var t3 = new thread(() => travelthroughgates("thread 3", 12));

            t1.start();

            t2.start();

            t3.start();

            thread.sleep(timespan.fromseconds(6));

            console.writeline("the gates are now open!");

            _mainevent.set();

            thread.sleep(timespan.fromseconds(2));

            _mainevent.reset();

            console.writeline("the gates have been closed!");

            thread.sleep(timespan.fromseconds(10));

            console.writeline("the gates are now open for the second time!");

            _mainevent.set();

            thread.sleep(timespan.fromseconds(2));

            console.writeline("the gates have been closed!");

            _mainevent.reset();

        }

        static void travelthroughgates(string threadname, int seconds)

        {

            console.writeline("{0} falls to sleep {1}", threadname, seconds);

            thread.sleep(timespan.fromseconds(seconds));

            console.writeline("{0} waits for the gates to open!", threadname);

            _mainevent.wait();

            console.writeline("{0} enters the gates!", threadname);

        }

        /// <summary>

        /// manualreseteventslim是manualresetevent的混合版本,一直保持大门敞开直到手工调用reset方法,

        /// _mainevent.set 相当于打开了大门从而允许准备好的线程接收信号并继续工作

        /// _mainevent.reset 相当于关闭了大门 此时已经准备好执行的信号量 则只能等到下次大门开启时才能够执行

        /// </summary>

        static manualreseteventslim _mainevent = new manualreseteventslim(false);

    }

}

 



【说明】本文章由站长整理发布,文章内容不代表本站观点,如文中有侵权行为,请与本站客服联系(QQ:254677821)!

相关教程推荐

其他课程推荐