【.NET】多线程:自动重置事件与手动重置事件的区别

这篇具有很好参考价值的文章主要介绍了【.NET】多线程:自动重置事件与手动重置事件的区别。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在多线程编程中,如果每个线程的运行不是完全独立的。那么,一个线程执行到某个时刻需要知道其他线程发生了什么。嗯,这就是所谓线程同步。同步事件对象(XXXEvent)有两种行为:

1、等待。线程在此时会暂停运行,等待其他线程发出信号才继续(等你约);

2、发出信号。当前线程发出信号,其他正在等待线程收到信号后继续运行(我约你)。

从前,小明、小伟、小更、小红、小黄计划到野外去烤鱼吃。但他们只确定市郊东南方向的一片区域,并不能保证具体哪个地点适合烧烤。于是,他们商量好,大家同时从家里出发。小明离那里比较近,他先去考察一下;其他人到了东南郊后集合,等小明的消息。小明考察完毕,向大家群发消息说明选定的地点是F。最后大家继续前行,奔向F。

等待事件有好几个:

1、Mutex:互斥体。一次只能有一个线程获取到互斥体,其他线程只能等。占用互斥体的线程释放后,其他线程继续抢 Mutex。然后只有一个线程能抢到,其他线程继续等……

2、AutoResetEvent:自动事件,发出信号后立刻重置。

3、ManualResetEvent:手动事件,发出信号后不会立刻重置,得手动重置。

4、CountdownEvent:这个和上面两个差不多。但它会设定一个计数,线程发出信号时会减少计数。被阻止的线程要等到计数 <= 0 时才获得信号。

 文章来源地址https://www.toymoban.com/news/detail-749824.html

本次咱们讨论的重点是看看自动重置信号和手动重置信号之间有什么区别。

 先看看自动重置的。

internal class Program
{

    static AutoResetEvent theEvent = new(false);

    static void Main(string[] args)
    {
        // 启动三个线程
        ThreadPool.QueueUserWorkItem(DoWorking, "A");
        ThreadPool.QueueUserWorkItem(DoWorking, "B");
        ThreadPool.QueueUserWorkItem(DoWorking, "C");
        // 主线程监听键盘消息
        while(true)
        {
            var keyInfo = Console.ReadKey(true);
            // 看看是不是Y键
            if(keyInfo.Key == ConsoleKey.Y)
            {
                // 点亮信号
                theEvent.Set();
            }
            // 输出一行,方便判断一个循环
            Console.WriteLine("------------------------------");
        }
    }

    static void DoWorking(object? state)
    {
        while(true)
        {
            // 等待主线程的信号
            // 此线程会暂停
            theEvent.WaitOne();
            // 得到信号了,继续运行
            Console.WriteLine("{0}已收到通知", state);
        }
    }
}

这个例子创建了三个线程,这里我用的是线程池,把一个WaitCallback委托传给 QueueUserWorkItem 方法就可以在线程池中运行新线程。上面示例中绑定的方法是 DoWorking。

AutoResetEvent 类的构造函数传了一个 bool 值,它的作用是设置等待事件的初始状态:

1、如果为 true,表示事件初始状态为打开信号,这会使正在等的线程马上得到信号;

2、如果为 false,表示事件的初始状态为没有信号,正在等待的线程继续等。

按照咱们这个例子的实际情况,我们一开始应该让事件无状态,让后台的三个线程等待。主线程读取按键信息,如果按的是【Y】键,那么事件调用 Set 方法,打开信号。此时,等得花儿都谢了的三个线程会继续。我们运行一下,看看能否符合预期。

【.NET】多线程:自动重置事件与手动重置事件的区别

经测试,我们会发现:每次按【Y】后,三个线程中只有一个获得信号并继续,其他两个还在高速上堵车。 AutoResetEvent 的自动重置就是打开信号后又立马关闭,每次只让一个线程收到信号。所以,当咱们按一次【Y】键后,主线程发出了信号,又马上关闭。三个后台线程相互竞争,随机获得机会,结束等待并继续运行。

 

手动重置事件在打开信号后,信号会持续有效,直到调用 Reset 方法手动关闭信号。手动重置信号能让多个线程有足够的时间收到信号。

下面咱们把上面的示例改为使用 ManualResetEvent 类。

internal class Program
{
    static ManualResetEvent theEvent = new(false);

    static void Main(string[] args)
    {
        // 启动三个线程
        ThreadPool.QueueUserWorkItem(DoWorking, "A");
        ThreadPool.QueueUserWorkItem(DoWorking, "B");
        ThreadPool.QueueUserWorkItem(DoWorking, "C");
        // 主线程监听键盘消息
        while(true)
        {
            var keyInfo = Console.ReadKey(true);
            // 看看是不是Y键
            if(keyInfo.Key == ConsoleKey.Y)
            {
                // 点亮信号
                theEvent.Set();

                // 持续一段时间后关闭信号
                Thread.Sleep(3);
                theEvent.Reset();
            }
            // 输出一行,方便判断一个循环
            Console.WriteLine("------------------------------");
        }
    }

    static void DoWorking(object? state)
    {
        while(true)
        {
            // 等待主线程的信号
            // 此线程会暂停
            theEvent.WaitOne();
            // 得到信号了,继续运行
            Console.WriteLine("{0}已收到通知", state);
        }
    }
}

然后运行程序,这一次按下【Y】键后,三个线程都能收到信号通知了。

【.NET】多线程:自动重置事件与手动重置事件的区别

你会发现,有些线程重复了多次,那是因为 DoWorking 方法里面是个死循环。当信号持续打开期间,三个线程都有机会收到信号,甚至会重复收到。

上面的东东纯属演示,实际使用的话不会这样设计。最好的方法是建一个列表对象,主线程接收到的按键字符存放到一个列表中,然后,后台线程不断地从列表中取出元素来处理。这样设计程序会更流畅。

internal class Program
{
    #region 字段区域
    static Queue<char> keyChars = new();
    #endregion

    static void Main(string[] args)
    {
        // 启动三个线程
        ThreadPool.QueueUserWorkItem(DoSomething, "A");
        ThreadPool.QueueUserWorkItem(DoSomething, "B");
        ThreadPool.QueueUserWorkItem(DoSomething, "C");

        while(true)
        {
            // 读取键盘字符
            ConsoleKeyInfo info = Console.ReadKey(true);
            // 将字符放入队列
            keyChars.Enqueue(info.KeyChar);
        }
    }

    static void DoSomething(object? state)
    {
        while(true)
        {
            // 锁定
            Monitor.Enter(keyChars);
            if (keyChars.Count > 0)
            {
                // 取掉一个元素
                char c = keyChars.Dequeue();
                Console.WriteLine($"线程【{state}】获得字符:{c}");
            }
            // 解锁
            Monitor.Exit(keyChars);
        }
    }
}

这里我用泛型队列 Queue<T> 来存放键盘敲入的字符,DoSomething 方法将放入线程池中运行。在从队列中取出元素并处理时,一定要记得上锁。我用的是 Monitor 对象的静态方法来上锁和解锁,当然你可以用 lock 语句块。

lock(keyChars)
{
    ……
}

如果不上锁,线程间在抢占资源时会导致不一致的状态。当A线程访问 keyChars.Count 属性时得到 1,还是 > 0 的,但在取出最后一个元素前,偏偏B线程动作快把最后一个元素拿走了。当A线程执行到 keyChars.Dequeue() 一句时,keyChars 队列中已经没有元素了,会发生错误。

主线程在 Enqueue 时并不需要锁定,因为元素送入队列只有一个线程在做,没人跟他抢资源,可以不锁定。

运行程序后,可以按字母、数字等按键来测试。毕竟像【F3】、【Ctrl】等按键获取到的是空白 char。

【.NET】多线程:自动重置事件与手动重置事件的区别

这样就顺畅很多了。

 

到了这里,关于【.NET】多线程:自动重置事件与手动重置事件的区别的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【多线程】线程池详解,常见的面试题,以及手动实现线程池

    前言: 大家好,我是 良辰丫 ,今天我们来学习一下线程池.线程池到底是什么呢?我们一起往下看💞💞💞 🧑个人主页:良辰针不戳 📖所属专栏:javaEE初阶 🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。 💦期待大家三连,关注,点赞,收

    2023年04月18日
    浏览(29)
  • 重置.NET Framework,修复因组件损坏造成的程序闪退问题

    重置 .NET Framework 至装机状态,修复因 .NET Framework 部分组件损坏而造成程序闪退的问题。 笔者因使用了IObit Uninstaler卸载了Visual Studio 2022而导致Visual Studio Installer闪退,想重新安装都不行,在事件查看器可以看到类似的如下报错,错误模块名称: KERNELBASE.dll,异常信息: System.Ty

    2024年02月04日
    浏览(48)
  • SourceTree中“提交回滚”和“重置到此次提交”的区别

    用一个例子来说明: 若只需要撤销2的提交,1、3保留,则点击记录2右键“提交回滚”,则本地的2被回滚,推送后线上分支也回滚了。(恢复刚刚到操作:点击“提交回滚”后的右键再次“提交回滚”,则撤销了刚刚的回滚,即1、2、3都在) 注意: 提交回滚时,2的提交记录

    2024年02月05日
    浏览(41)
  • Observability:使用 OpenTelemetry 手动检测 .NET 应用程序

    作者:David Hope 在快节奏的软件开发领域,尤其是在云原生领域,DevOps 和 SRE 团队日益成为应用程序稳定性和增长的重要合作伙伴。 DevOps 工程师不断优化软件交付,而 SRE 团队则充当应用程序可靠性、可扩展性和顶级性能的管理者。 挑战? 这些团队需要一种尖端的可观察性

    2024年02月06日
    浏览(59)
  • 手动渲染农场和自助云渲染农场的区别

    手动渲染农场和自助云渲染农场是两种常见的渲染方式,它们各有优缺点。手动渲染农场指的是在本地使用自己的硬件设备进行渲染,而自助云渲染农场则是利用云服务商提供的计算资源进行渲染。对于需要渲染大规模项目的设计师或工作室来说,选择一种合适的渲染方式可

    2024年02月09日
    浏览(47)
  • Android 自动滚动的RecyclerView,手动滑动和自动滑动无缝衔接,手动滑动时数据不重复

    概要 做一个自动滑动的列表,用于展示聊天记录或者通知栏信息等,还是使用主流的RecyclerView来做。网上有很多案例,但当手动滑动时会一直无限循环,数据重复的出现,如果想要自动滑动时能无限循环,手动滑动时又能滑到底呢?本案例就解决这种手动滑动和自动滑动无缝

    2024年01月23日
    浏览(49)
  • Access自动编号的初始值设置及重置编号问题

    在做ASP网站的注册会员功能,希望实现一个 用户在注册后数据库把新加入记录的自动编号ID反馈给用户,用户会得到这个ID号(像QQ号一样),作为用户的独有账户名。 发现ID是从“1”开始的。这未免有点太不靠谱:一个用户注册完会员,得到一个“1”作为账号,呵呵,太可笑

    2024年02月07日
    浏览(37)
  • rabbitMQ手动应答与自动应答

    手动应答模式(manual) 解释:         手动应答:既是当消费者消费了队列中消息时需要给队列一个应答,告诉队列这条消息我已经消费了,可以删除了;         若是不应答,即使消费了 队列没收到消费成功的提示 所有消息会一直在队列中;      注意 注意 注意 :重要的事情说三

    2024年02月10日
    浏览(35)
  • 如何手动搭建自动化部署系统

    前两天写了个脚本帮助组内同学将本地构建产物上传至服务器,可以自动创建路径,监测是否存在历史版本,并将最新上传的产物替换历史版本,历史版本变为回溯版本。 核心就是: shell 脚本的复制 scp 指令。 sshpass 免交互 ssh 登录工具。 上面的脚本可以通过手动执行脚本,

    2024年02月11日
    浏览(68)
  • 汇总Kafka手动提交与自动提交

    程序拉取消息后,满足要求后自动提交,无需程序开发者介入。 2.1丢消息 在consumer拉取消息之后,达到提交时间AUTO_COMMIT_INTERBAL_MS_CONFIG之后位移(例如1-10)已经自动提交,若此时消息尚未消费完成时,且消费者挂掉了,此时尚未消费的消息会丢失。因为位移1-10已经提交,下

    2024年02月12日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包