从C#5.0说起:再次总结C#异步调用方法发展史

news2024/11/11 8:02:50

本篇继续介绍WaitHandler类及其子类 Mutex,ManualResetEvent,AutoResetEvent的用法。

.NET中线程同步的方式多的让人看了眼花缭乱,究竟该怎么去理解呢?

其实,我们抛开.NET环境看线程同步,无非是执行两种操作:

  • 一是互斥/加锁,目的是保证临界区代码操作的“原子性”;
  • 另一种是信号灯操作,目的是保证多个线程按照一定顺序执行,如生产者线程要先于消费者线程执行。

.NET中线程同步的类无非是对这两种方式的封装,目的归根结底都可以归结为实现互斥/ 加锁或者是信号灯这两种方式,只是它们的适用场合有所不。

下面我们根据类的层次结构了解WaitHandler及其子类。

1.WaitHandler

WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,它封装Win32同步句柄内核对象,也就是说是这些内核对象的托管版本。

线程可以通过调用WaitHandler实例的方法WaitOne在单个等待句柄上阻止。此外,WaitHandler类重载了静态方法,以等待所有指定的等待句柄都已收集到信号WaitAll,或者等待某一指定的等待句柄收集到信号WaitAny。这些方法都提供了放弃等待的超时间隔、在进入等待之前退出同步上下文的机会,并允许其它线程使用同步上下文。WaitHandler是C#中的抽象类,不能实例化。

2.EventWaitHandler vs. ManualResetEvent vs. AutoResetEvent(同步事件)

我们先看看两个子类ManualResetEvent和AutoResetEvent在.NET Framework中的实现:
//.NET Framework中ManualResetEvent类的实现 [ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)] public sealed class ManualResetEvent : EventWaitHandle { // Methods public ManualResetEvent(bool initialState) : base(initialState, EventResetMode.ManualReset) { } } //.NET Framework中AutoResetEvent类的实现 [ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)] public sealed class AutoResetEvent : EventWaitHandle { // Methods public AutoResetEvent(bool initialState) : base(initialState, EventResetMode.AutoReset) { } }

原来ManualResetEvent和AutoResetEvent都继承自EventWaitHandler,它们的唯一区别就在于父类 EventWaitHandler的构造函数参数EventResetMode不同,这样我们只要弄清了参数EventResetMode值不同时,EventWaitHandler类控制线程同步的行为有什么不同,两个子类也就清楚了。为了便于描述,我们不去介绍父类的两种模式,而直接介绍子类。

ManualResetEvent和AutoResetEvent的共同点:

1)Set方法将事件状态设置为终止状态,允许一个或多个等待线程继续;Reset方法将事件状态设置为非终止状态,导致线程阻止;WaitOne阻止当前线程,直到当前线程的WaitHandler收到事件信号。
2)可以通过构造函数的参数值来决定其初始状态,若为true则事件为终止状态从而使线程为非阻塞状态,为false则线程为阻塞状态。
3)如果某个线程调用WaitOne方法,则当事件状态为终止状态时,该线程会得到信号,继续向下执行。

ManualResetEvent和AutoResetEvent的不同点:

1)AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,则其他调用WaitOne的线程只有继续等待,也就是说AutoResetEvent一次只唤醒一个线程;
2)ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为不发送。
3)也就是说,除非手工调用了ManualResetEvent.Reset()方法,则ManualResetEvent将一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。

示例场景:张三、李四两个好朋友去餐馆吃饭,两个人点了一份宫爆鸡丁,宫爆鸡丁做好需要一段时间,张三、李四不愿傻等,都专心致志的玩起了手机游戏,心想宫爆鸡丁做好了,服务员肯定会叫我们的。服务员上菜之后,张三李四开始享用美味的饭菜,饭菜吃光了,他们再叫服务员过来买单。我们可以从这个场景中抽象出来三个线程,张三线程、李四线程和服务员线程,他们之间需要同步:服务员上菜—>张三、李四开始享用宫爆鸡丁—>吃好后叫服务员过来买单。这个同步用什么呢? ManualResetEvent还是AutoResetEvent?通过上面的分析不难看出,我们应该用ManualResetEvent进行同步,下面是程序代码:
https://www.cnblogs.com/dotnet261010/p/6055092.html
张三李四吃饭的故事
————————————————
C#发展至今,已经从最初的1.0到了5.0版本,其进化史如下,参考了C# 5.0 IN A NUTSHEL:
在这里插入图片描述

让我们来回顾一下各个版本都带来了什么:

  1. 1.0版本 - 基本C#语法。
  2. 2.0版本 - 泛型的支持,CLR进行了升级,从根本上支持了运行时泛型。
  3. 3.0版本 - LINQ,添加了from / join等类SQL关键字,添加了扩展函数,添加了编译期动态类型var关键字。
  4. 4.0版本 - dynamic关键字,CLR进行升级,加入DLR,开始对动态进行友好的支持。同时加入动态参数、参数默认值、泛型协变等特性。
  5. 5.0版本-新的异步模型,新增了async/await等关键字,简化并行计算Parallel。
    可以看出作为编程语言的C#已经非常强大,单随着时代的发展,C#依然在不断的前进。每一代的C#都会在语法的调整之,外带来一个新特性。从2.0的泛型、3.0的LINQ、4.0的dynamic到5.0的Async异步,每个版本的C#都有一个主导的思想,而其他细节的改进和调整则是围绕着这个主导思想给予支持。
    下面我们来看下C#5.0及之前版本,异步调用方法的各种实现方法。
    首先我们来看一个普通的同步方法,如下:
using System;
using System.Net;

namespace NoAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            ShowUriContent("http://www.cnblogs.com/DebugLZQ");
        }

        static void ShowUriContent(string uri)
        {
            using (WebClient client = new WebClient())
            {
                string text = client.DownloadString(uri);
                Display(text);
            }
        }

        static void Display(string text)
        {
            Console.WriteLine(text.Length);
        }
    }
}

同步方法会造成线程的阻塞。
因此我们有了异步的方法,最早期的异步方法时Begin/End模式(其实现方法一共有四种,请参考DebugLZQ前面的博文:.NET异步编程总结----四种实现模式)。
我们用Begin/End推荐模式来封装这个同步方法以实现异步调用,如下:

using System;
using System.Threading;
using System.Net;

namespace AsyBeginEndNoEncapsulation
{
    class Program
    {
        static void Main(string[] args)
        {
            ShowUriContent("http://www.cnblogs.com/DebugLZQ");//原同步方法
            ShowUriContentAsync("http://www.cnblogs.com/DebugLZQ");//封装后的异步方法

            Thread.Sleep(5000);
        }
        //------进行异步封装
        public delegate void ShowUriContentDelegate(string text);
        static void ShowUriContentAsync(string uri)
        {
            ShowUriContentDelegate showUriContentDelegate = ShowUriContent;
            showUriContentDelegate.BeginInvoke(uri, ShowUriContentCompleted, showUriContentDelegate);
        }

        static void ShowUriContentCompleted(IAsyncResult result)
        {
            (result.AsyncState as ShowUriContentDelegate).EndInvoke(result);
        }
        //------原同步方法
        static void ShowUriContent(string uri)
        {
            using (WebClient client = new WebClient())
            {
                string text = client.DownloadString(uri);
                Display(text);
            }
        }

        static void Display(string text)
        {
            Console.WriteLine(text.Length);
        }
    }
}

最原始的封装就是这个样子。
可以利用C#新特性,如Action/Function、匿名方法、Lambda表达式等,简写(合写)如下:

using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
using System.Threading;
using System.Net;

namespace AsyBeginEndNoEncapsulationSimply
{
    class Program
    {
        static void Main(string[] args)
        {
            ShowUriContent("http://www.cnblogs.com/DebugLZQ");//原同步方法
            ShowUriContentAsync("http://www.cnblogs.com/DebugLZQ");  //进行异步封装
            ShowUriContentAsync1("http://www.cnblogs.com/DebugLZQ");//简化1:Action简化
            ShowUriContentAsync2("http://www.cnblogs.com/DebugLZQ");//简化2:匿名方法简化
            ShowUriContentAsync3("http://www.cnblogs.com/DebugLZQ");//简化3:Lambda简化
            

            Thread.Sleep(50000);
        }
        //------进行异步封装
        public delegate void ShowUriContentDelegate(string text);
        static void ShowUriContentAsync(string uri)
        {
            ShowUriContentDelegate showUriContentDelegate = ShowUriContent;
            showUriContentDelegate.BeginInvoke(uri, ShowUriContentCompleted, showUriContentDelegate);
        }

        static void ShowUriContentCompleted(IAsyncResult result)
        {
            (result.AsyncState as ShowUriContentDelegate).EndInvoke(result);
        }
        //------进行异步封装--简化1:Action简化
        static void ShowUriContentAsync1(string uri)
        {
            Action<string> showUriContentDelegate = ShowUriContent;
            showUriContentDelegate.BeginInvoke(uri, ShowUriContentCompleted1, showUriContentDelegate);
        }

        static void ShowUriContentCompleted1(IAsyncResult result)
        {
            (result.AsyncState as Action<string>).EndInvoke(result);
        }
        //------简化2:匿名方法简化
        static void ShowUriContentAsync2(string uri)
        {
            Action<string> showUriContentDelegate = delegate(string uri_)
            {
                using (WebClient client = new WebClient())
                {
                    string text = client.DownloadString(uri_);
                    Display(text);
                }
            };
            showUriContentDelegate.BeginInvoke(uri, delegate(IAsyncResult result) { (result.AsyncState as Action<string>).EndInvoke(result); }, showUriContentDelegate);
        }
        //------简化3:Lambda简化
        static void ShowUriContentAsync3(string uri)
        {
            Action<string> showUriContentDelegate = ( uri_)=>
            {
                using (WebClient client = new WebClient())
                {
                    string text = client.DownloadString(uri_);
                    Display(text);
                }
            };
            showUriContentDelegate.BeginInvoke(uri, (result) => { (result.AsyncState as Action<string>).EndInvoke(result); }, showUriContentDelegate);
        }       
       
        //---------------------原同步方法
        static void ShowUriContent(string uri)
        {
            using (WebClient client = new WebClient())
            {
                string text = client.DownloadString(uri);
                Display(text);
            }
        }

        static void Display(string text)
        {
            Console.WriteLine(text.Length);
        }
    }
}

以上是我们最原始的实现方法,及利用新特性的各种变种写法。
但是WebClient作为WebRequest的高层封装,.NET已经帮我们把这个异步模式给封装了(也就是说有些同步方法不需要我们自己进行封装了)。
因此我们也可以如下:

 
using System;
using System.Threading;
using System.Net;

namespace AsyncBeginEndEncapsulation
{
    class Program
    {
        static void Main(string[] args)
        {
            ShowUriContent("http://www.cnblogs.com/DebugLZQ");
            ShowUriContent2("http://www.cnblogs.com/DebugLZQ");

            Console.WriteLine("Main thread continue...");
            Thread.Sleep(5000);
        }

        static void ShowUriContent(string uri)
        {
            using (WebClient client = new WebClient())
            {
                client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(Display);
                client.DownloadStringAsync(new Uri(uri));
            }
        }

        static void Display(object sender, DownloadStringCompletedEventArgs e)
        {
            Console.WriteLine(e.Result.Length);
        }

        //-------简化的写法
        static void ShowUriContent2(string uri)
        {
            using (WebClient client = new WebClient())
            {
                client.DownloadStringCompleted += (s, e) => { Console.WriteLine(e.Result.Length); };
                client.DownloadStringAsync(new Uri(uri));
            }
        }
    }
}

从上面.NET对Begin/End模式的主动封装可以看出,其目的是为了简化异步方法的调用,最终的目的是让异步方法调用像我们最熟悉的同步方法调用那么简单。
C#5.0引入了两个关键字async、await以提供一种更为简洁的异步方法调用模式。
我们实现如下(控制台入口点Main方法无法标记为async,因此我们用Winform程序演示):

using System;
using System.Windows.Forms;
using System.Net;

namespace AsyncAwaitWinForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            int length = await ShowUriContentAsyncAwait("http://www.cnblogs.com/DebugLZQ");
            textBox1.Text = length.ToString();
        }

         //
        async Task<int> ShowUriContentAsyncAwait(string uri)
        {
            using (WebClient client = new WebClient())
            {
                string text = client.DownloadString(uri);
                return text.Length;
            }
        }     
    }
}

需要说明的是async、await需要:Visual Studio 2010 + SP1+Visual Studio Async CTP,或是Visual Studio 2012.

Update: 关于 Parallel—Task,请参考DebugLZQ后续博文:Task and Parallel

update:关于Async await详细,请参考DebugLZQ后续博文:
async wait

以上所有示例程序均由DebugLZQ动手编写,可以正常运行,其结果显而易见,因此没有附上运行截图。
科技不断向前发展,期待以后版本的C#6,7,8,X…带来的更多精彩~
把自己的理解分享给大家,共同交流进步,认识不断提升~

Update: Read more
Difference between Delegate.BeginInvoke and Thread.Start
Differences in the different ways to make concurrent programs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试基础知识总览【纯知识,建议收藏慢慢学】

1. 软件测试定义 首先要明确测试的定义,所谓测试,就是以检验产品是否满足需求为目标。 而软件测试,自然是为了发现软件(产品)的缺陷而运行软件(产品) 比较标准的软件测试的定义是:在规定的条件下对程序进行操作&#xff0c;以发现错误&#xff0c;对软件质量进行评估。 IEE…

算法总结,不断更新

文章目录摩尔投票法DFS算法BFS算法题源来自于力扣网 摩尔投票法 适用场景 如何在选票无序的情况下&#xff0c;选出获胜者。 例题&#xff1a; 找出数组中&#xff0c;出现次数超过总数一半的数字&#xff08;出现次数 > n/2&#xff09;。 输入&#xff1a;[1,1,3,2,4,6,…

10000字吐血总结+24张图带你彻底弄懂线程池

大家好。今天跟大家聊一聊无论是在工作中常用还是在面试中常问的线程池&#xff0c;通过画图的方式来彻底弄懂线程池的工作原理&#xff0c;以及在实际项目中该如何自定义适合业务的线程池。 一、什么是线程池 线程池其实是一种池化的技术的实现&#xff0c;池化技术的核心思…

MVC|JAVA|SSM框架计算机硬件评测交流平台的开发和实现

收藏点赞不迷路 关注作者有好处 文末获取源码 项目编号&#xff1a;BS-PT-070 一&#xff0c;项目简介 计算机硬件在社会上有很多广泛的发烧友&#xff0c;他们急需一个发布专业硬件测评数据的平台并进行交流互动的社区。本次开发实现的计算机硬件交流平台就是作为一个专业的…

Android序列化之Parcel源码分析(2)

文章目录1.Parcel.java2.Parcelable和Parcel的关系3.Parcel写入数据源码分析3.1.java层Parcel创建3.2.native层Parcel创建3.3写入IBinder接口标识符3.4写入String数据4.Parcel读取数据源码分析4.1获取IBinder接口标识符4.2读取String数据1.Parcel.java Android可以通过Parcel进…

【OpenCV学习】第15课:处理卷积边缘问题

仅自学做笔记用,后续有错误会更改 &#xff08;卷积的概念可以看看第14课&#xff09; 理论 卷积边缘问题&#xff1a;从下图最右方的结果可以看出&#xff0c;卷积操作之后&#xff0c; 剩余的绿色像素部分&#xff0c; 我们是没有处理到的 那么如何处理这个问题呢&#xf…

论文3:查找文献在指定期刊的引用格式

文章目录说明&#xff1a;1.谷歌学术搜索&#xff08;可以用一些国内的镜像&#xff09;&#xff0c;并点击被引用次数2.勾选在引用文章中搜索&#xff0c;并在搜索框搜索指定期刊的关键词3.这里指定期刊是RAL即IEEE Robotics and Automation Letters4.任意点开上图中的一篇文章…

支付宝当面付网站对接支付教程

有很多人会开支付宝当面付但是不会配置它老会出现一下情况 第二种情况如下&#xff1a; 如果遇到以上情况可以按照我的步骤就可以解决 详细步骤&#xff1a; 一、应用APPID获取方法 1.打开网站&#xff1a;https://openhome.alipay.com/platform/developerIndex.htm&#x…

Canal配置多个实例以及将Mysql指定表的binlog导入指定的Kafka的Topic

Canal配置多个实例以及将Mysql指定表的binlog导入指定的Kafka的Topic 进入Canal的conf目录 复制模板配置文件 cp -r example/ Ordercp -r example/ Orderdetail修改canal.propertieswenjain vim canal.properties修改内容如下&#xff0c;指定输出模式为kafka canal.serverM…

【元胞自动机】心房颤动/扑动模型研究(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

(附源码)ssm学校疫情服务平台 毕业设计 291202

ssm学校疫情服务平台 摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对学校疫情服务平台等问…

【数电实验】触发器及其应用

实验三 触发器及其应用 一 实验目的 1 了解触发器的触发方式&#xff08;上升沿触发、下降沿出发&#xff09;及其触发特点&#xff1b; 2 测试常用触发器的逻辑功能&#xff1b; 3 掌握用触发器设计同步时序逻辑电路的方法。 二 实验内容 1 测试双D触发器74HC74的逻辑功能…

手工编译konsole备忘

背景 系统自带的终端弱爆了&#xff0c;本来想编译深度终端的&#xff0c;但DTK风格的程序在非DDE桌面&#xff08;应该是dde_kwin这个窗管的问题&#xff09;巨难看&#xff0c;无意中添加了Konsole&#xff0c;发现已经有我需要使用的右键打开当前目录文件管理器的功能。 …

Go context.Context的学习

一、前言 Golang context是Golang应用开发常用的并发控制技术&#xff0c;它与WaitGroup最大的不同点是context对于派生goroutine有更强的控制力&#xff0c;它可以控制多级的goroutine。 context翻译成中文是”上下文”&#xff0c;即它可以控制一组呈树状结构的goroutine&a…

java计算机毕业设计ssm疫情期间校园车辆入校预约管理服务系统1171a(附源码、数据库)

java计算机毕业设计ssm疫情期间校园车辆入校预约管理服务系统1171a&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe…

没有二十年功力,写不出 Thread.sleep(0) 这一行“看似无用”的代码

这篇文章要从一个奇怪的注释说起&#xff0c;就是下面这张图&#xff1a; 我们可以不用管具体的代码逻辑&#xff0c;只是单单看这个 for 循环。 在循环里面&#xff0c;专门有个变量 j&#xff0c;来记录当前循环次数。 第一次循环以及往后每 1000 次循环之后&#xff0c;进…

ssm+vue基本微信小程序的校园二手商城系统 计算机毕业设计

在当今社会的高速发展过程中&#xff0c;产生的劳动力越来越大&#xff0c;提高人们的生活水平和质量&#xff0c;尤其计算机科技的进步&#xff0c;数据和信息以人兴化为本的目的&#xff0c;给人们提供优质的服务&#xff0c;其中网上购买二手商品尤其突出&#xff0c;使我们…

211大数据专业大四学生,放弃字节转正,选择老家大型国企,听听他怎么说?...

点击上方 "大数据肌肉猿"关注, 星标一起成长点击下方链接&#xff0c;进入高质量学习交流群今日更新| 1052个转型案例分享-大数据交流群分享学习群一位大数据专业同学的秋招学习和求职经历&#xff0c;他是211大四学生&#xff0c;年初才开始学习&#xff0c;但还好赶…

181.基于Django的云文件存储使用方式——七牛云存储

1.文件云存储 1.1 概述 在Django项目中&#xff0c;用户上传的文件以及项目中使用的静态文件&#xff0c;默认读书存储本地&#xff0c;保存在服务器中&#xff0c;但是&#xff0c;其实我们也可以将他们保存在云存储中&#xff0c;譬如七牛云存储、阿里云存储、亚马逊云存储…

【网络安全】提防黑客来“敲门”

前言 互联网在给我们带来便捷高效的同时&#xff0c;也给一些不法分子提供了可乘之机。网络诈骗、窃取个人信息等花样层出不穷&#xff0c;骚扰电话、垃圾短信扰乱着我们的正常生活&#xff0c;使网络空间抹上一笔灰色。网络安全与每个人都息息相关&#xff0c;所以我们必须理…