学习多线程之前我们先了解一下电脑的一些概念,比如进程,线程,这个参考https://www.cnblogs.com/loverwangshan/p/10409755.html 这篇文章。今天我们接着来介绍同步方法和异步方法。

一:同步方法:在程序继续执行之前需要等待同步方法执行完毕返回结果

通俗的例子就是: 邀请wss次饭,wss要忙一会儿,邀请人等着wss完成后,再一起去吃饭,这就是所谓的诚心诚意的请人吃饭。下面我通过代码来举例来说明一下同步方法:

 /// <summary>
/// 一个比较耗时耗资源的私有方法
/// </summary>
/// <param name="name"></param>
private void DoSomethingLong(string name)
{
Console.WriteLine($"******DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}*******");
long lResult = ;
for (int i = ; i < 1_000_000_000; i++)
{
lResult += i;
}
Console.WriteLine($"*****DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}*********");
} /// <summary>
/// 同步方法
/// </summary>
private void syncWay()
{
Console.WriteLine($"******同步方法开始执行,执行线程ID:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】,执行开始时间:【 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}】*****");
for (int i = ; i < ; i++)
{
string name = string.Format($"sysnc_{i}");
this.DoSomethingLong(name);
}
Console.WriteLine($"****同步方法执行结束,执行线程ID:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】,执行开始时间:【 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}】******");
}

直接调用执行 syncWay(); 则会输出如下图:

我们可以通过上图晓得:DoSomethingLong是一步一步执行的,而且同步方法的线程Id都是一致的,也可以看出执行过程的耗时也蛮长。

二:异步方法:在被调用之后立即返回以便程序在被调用方法完成其任务的同时执行其它操作

通俗的例子就是: 邀请wss次饭,wss要忙一会儿,然后wss自己先忙着吧,邀请人先去吃饭了,wss忙完自己去吃饭吧,这就是所谓的客气一下的请人吃饭。下面我接着使用实例来认识异步方法:

 /// <summary>
/// 一个比较耗时耗资源的私有方法
/// </summary>
/// <param name="name"></param>
private void DoSomethingLong(string name)
{
Console.WriteLine($"*****DoSomethingLong开始;参数【{name}】;线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】;当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***");
long result = ;
for (int i = ; i < 1_000_000_000; i++)
{
result += i;
}
Console.WriteLine($"*****DoSomethingLong结束;参数【{name}】;线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】;当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")};result:{result}***");
} private void AsyncWay()
{
Console.WriteLine($"******异步方法开始执行,执行线程ID:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】,执行开始时间:【 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}】*****");
Action<string> action = this.DoSomethingLong;
for (int i = ; i < ; i++)
{
string name = string.Format($"async_{i}");
action.BeginInvoke(name, null, null);//委托自身需要的参数 + 2个异步参数(这两个参数我们在下面详细说明)
}
Console.WriteLine($"****异步方法执行结束,执行线程ID:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】,执行开始时间:【 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}】******");
}

然后通过执行AsyncWay()这个方法,会得到如下截图:

我们可以通过上面的图得到如下几点:

1:不卡主线程改善用户体验。web或者winform如果处理耗时的任务,异步方法会使页面或者winform不卡界面,这主要是主线程(线程01)闲置,计算任务交给子线程(线程03,04,05,06)完成。这个可以改善用户体验。一般在web应用中异步方法会做发短信/记日志等功能。

2:异步方法耗时短,这个是最明显的。通过我们观察cpu的曲线图,会总结出:多线程其实是资源换性能。但是也不能乱用多线程,主要是

  • A: 资源不是无限的
  • B: 资源调度损耗(这个电脑配置有关系)

3:异步方法是无序的,主要体现在如下:

  • A:启动无序,因为线程资源是向操作系统申请的,由操作系统的调度策略决定,所以启动顺序随机
  • B:同一个任务同一个线程,执行时间也不确定,CPU分片
  • C:由上面两条也得到结束也无序

.NET Framework 允许异步调用任何方法。定义与您需要调用的方法具有相同签名的委托;公共语言运行库将自动为该委托定义具有适当签名的 BeginInvoke 和 EndInvoke 方法。
BeginInvoke 方法用于启动异步调用。它与您需要异步执行的方法具有相同的参数,只不过还有两个额外的参数

    • 第一个参数表示一个委托,需要传递一个方法,当该线程结束时会调用这个方法
    • 第二个参数可以设置为任意对象,以便在回调函数中访问它,在这里所用的是上面定义的委托a。

三:异步方法如何控制顺序?

就是说我们想要在一个异步方法全部执行完成后再执行某一部分内容,可以通过以下几种方式来实现:

1:BeginInvoke 立即返回,不等待异步调用完成。

使用BeginInvoke的AsyncCallback,将后续动作通过回调参数AsyncCallback传递进去,子线程完成计算后,去调用这个回调委托,BeginInvoke这个我们上面有提到,下面还是以代码来解释说明。

 private void FirstWay()
{
Console.WriteLine($"**开始线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}**"); Action<string> action = this.DoSomethingLong;
IAsyncResult asyncResult = null;//是对异步调用操作的描述 //回调委托方法:将后续动作通过回调参数传递进去,子线程完成计算后,去调用这个回调委托
AsyncCallback callback = ar =>
{
Console.WriteLine($"asyncResult和ar是否是同一个对象:{object.ReferenceEquals(ar, asyncResult)}"); //可以说明ar就是asyncResult
Console.WriteLine($"dosomething计算成功了。ar.AsyncState=【{ar.AsyncState}】。线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】");
};
//第1个参数:是action需要的参数;
//第2个参数:AsyncCallback,就是执行完action方法后要执行的内容;
//第3个参数:是一个object的对象,指的是IAsyncResult函数中的AsyncState,可以作为参数等传进去
asyncResult = action.BeginInvoke("异步控制顺序", callback, "ar.AsyncState参数");
Console.WriteLine($"**结束:线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}**");
}

执行FirstWay()会得到如下结果:

我们通过上图可以分析出:

  • A:BeginInvoke是一个异步方法,因为主线程【01】只负责打印,而DoSomethingLong这个方法是开启一个新的线程【03】执行
  • B:AsyncCallback这个委托的参数其实是 action.BeginInvoke返回的结果类型为IAsyncResult,就是例子中的asyncResult
  • C:action.BeginInvoke第三个参数就是IAsyncResult中的AsyncState,是一个object类型,可以作为参数传入到AsyncCallback函数中
  • D:action.BeginInvoke这个函数的执行顺序是:先执行Action函数,然后执行完返回一个IAsyncResult结果asyncResult,然后把asyncResult作为参数传入到AsyncCallback这个委托中,最后再执行AsyncCallback委托

2:BeginInvoke 返回 IasyncResult,属性IAsyncResult.IsCompleted可用于监视调用进度。

  private void SecondWay()
{
Console.WriteLine($"**开始线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}**");
Action<string> action = this.DoSomethingLong;
IAsyncResult asyncResult = action.BeginInvoke("异步控制顺序", null, null); //通过IsComplate等待,卡界面--主线程在等待,边等待边提示
int i = ;
while (!asyncResult.IsCompleted)
{
if (i < )
{
Console.WriteLine($"DoSomethingLong完成{++i * 10}%....");
}
else
{
Console.WriteLine($"DoSomethingLong完成99.999999%....");
}
Thread.Sleep();
}
Console.WriteLine("DoSomethingLong完全执行结束!");
Console.WriteLine($"**结束:线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}**");
}

执行SecondWay()会返回如下:

3:使用 WaitOne等待,即时等待  限时等待,示例代码如下:

  private void ThirdWay()
{
Console.WriteLine($"**开始线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}**");
Action<string> action = this.DoSomethingLong;
IAsyncResult asyncResult = action.BeginInvoke("第三种异步控制顺序", null, null);
asyncResult.AsyncWaitHandle.WaitOne();//直接等待任务完成
//asyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任务完成
//asyncResult.AsyncWaitHandle.WaitOne(1000);//最多等待1000ms,超时就不等了 Console.WriteLine("DoSomethingLong完全执行结束!");
Console.WriteLine($"**结束:线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}**");
}

执行 ThirdWay()得到以下结果:

4:使用EndInvoke :即时等待,而且可以获取委托的返回值 一个异步操作只能End一次,示例代码如下:

EndInvoke 方法用于检索异步调用结果。调用 BeginInvoke 后可随时调用 EndInvoke 方法;如果异步调用未完成,EndInvoke 将一直阻塞到异步调用完成。
EndInvoke 的参数包括您需要异步执行的方法的 out 和 ref 参数(在 Visual Basic 中为 <Out> ByRef 和 ByRef)以及由BeginInvoke 返回的 IAsyncResult。

 private void ForthWay()
{
Console.WriteLine($"**开始线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}**"); Action<string> action = this.DoSomethingLong;
IAsyncResult asyncResult4Action = action.BeginInvoke("第四种EndInvoke异步控制顺序", null, null);
action.EndInvoke(asyncResult4Action); Func<int> func = () => { return ; };
IAsyncResult asyncResult4Fun = func.BeginInvoke(ar =>
{
Console.WriteLine($"IAsyncResult中的线程Id:【{ Thread.CurrentThread.ManagedThreadId.ToString("")}】");
}, null);
int result=func.EndInvoke(asyncResult4Fun);//里面的参数不能为空 Console.WriteLine($"endInvoke返回的func委托的值:{result}");
Console.WriteLine("DoSomethingLong完全执行结束!");
Console.WriteLine($"**结束:线程Id:【{Thread.CurrentThread.ManagedThreadId.ToString("")}】时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}**");
}

执行ForthWay()会得到如下:

上面的代码有Action和Fun两个方法,然后执行有时候会出现上图的结果,IAsyncResult最后一个输出来,通过这个我们可以得知: EndInvoke是让主线程的动作,等着子线程完成,但是这个等待并不会等待回调函数。所以会出现上面的结果

说明:以上四种方法中DoSomethingLong这个函数我们都使用上面异步或者同步方法的示例

通过以上4种方法总结得出,使用 BeginInvoke 和 EndInvoke 进行异步调用的常用方法。调用了 BeginInvoke 后,可以:

  1. 进行某些操作,然后调用 EndInvoke 一直阻塞到调用完成。
  2. 使用 IAsyncResult.AsyncWaitHandle 获取 WaitHandle,使用它的 WaitOne 方法将执行一直阻塞到发出 WaitHandle 信号,然后调用EndInvoke。这里主要是主程序等待异步方法,等待异步方法的结果。
  3. 轮询由 BeginInvoke 返回的 IAsyncResult,IAsyncResult.IsCompeted确定异步调用何时完成,然后调用 EndInvoke。此处理个人认为与相同。
  4. 将用于回调方法的委托传递给 BeginInvoke。该方法在异步调用完成后在 ThreadPool 线程上执行,它可以调用 EndInvoke。这是在强制装换回调函数里面IAsyncResult.AsyncState(BeginInvoke方法的最后一个参数)成委托,然后用委托执行EndInvoke。
  5. 警告 始终在异步调用完成后调用 EndInvoke。

c#委托中的同步和异步方法即BeginInvoke和EndInvoke的更多相关文章

  1. C#中的线程一(委托中的异步)

    C#中的线程一(委托中的异步) 一.同步委托 我们平时所用的委托以同步居多,我们编写一个方法和相关委托进行演示: publicdelegatevoid DoSomethingDelegate(stri ...

  2. C#中的线程(中)-线程同步

    1.同步要领 下面的表格列展了.NET对协调或同步线程动作的可用的工具:                       简易阻止方法 构成 目的 Sleep 阻止给定的时间周期 Join 等待另一个线程 ...

  3. nodejs递归创建目录,同步和异步方法

    nodejs递归创建目录,同步和异步方法.在官方API中只提供了最基本的方法,只能创建单级目录,如果要创建一个多级的目录(./aaa/bbb/ccc)就只能一级一级的创建,感觉不是很方便,因此简单写了 ...

  4. 利用BenchmarkDotNet 测试 .Net Core API 同步和异步方法性能

    事由: 这两天mentor给我布置了个任务让我用BenchmarkDotNet工具去测试一下同一个API 用同步和异步方法写性能上有什么差别. 顺带提一下: 啊啊啊啊 等我仔细看文档的时候文档 发现它 ...

  5. 关于js中的同步和异步

    最近看到前端面试问到js中的同步和异步,这个问题该怎么回答? 梳理一下,js对于异步的处理,很多人的第一反应是ajax,这只能说是对了一半. 1.个人觉得,js中,最基础的异步是setTimeout和 ...

  6. CUDA 程序中的同步

    前言 在并发,多线程环境下,同步是一个很重要的环节.同步即是指进程/线程之间的执行顺序约定. 本文将介绍如何通过共享内存机制实现块内多线程之间的同步. 至于块之间的同步,需要使用到 global me ...

  7. c#委托中另外一种用法

    在c#委托中,经常可能遇到函数重载的情况,可是又需要在一个函数中调用这些函数,一般我都是根据多个函数重载个数,也写上这么多个函数重载.比如 public double T1(int r) { retu ...

  8. 泛型委托及委托中所涉及到匿名方法、Lambda表达式

    泛型委托及委托中所涉及到匿名方法.Lambda表达式 引言: 最初学习c#时,感觉委托.事件这块很难,其中在学习的过程中还写了一篇学习笔记:委托.事件学习笔记.今天重新温故委托.事件,并且把最近学习到 ...

  9. Service 中添加同步块防止并发 重复

    Service 中添加同步块防止并发 重复. synchronized(this){}

随机推荐

  1. Vue(二十七)当前GitHub上排名前十的热门Vue项目(转载)

    原文地址:https://my.oschina.net/liuyuantao/blog/1510726 1. ElemeFE/element tag:vue javascript components ...

  2. golang ntp协议客户端

    NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议,用来在分布式时间服务器和客户端之间进行时间同步.NTP基于UDP报文进行传输,使用的UDP端口 ...

  3. vue中计算属性computed方法内传参

    vue中computed计算属性无法直接进行传参 如果有传参数的需求比如说做数据筛选功能可以使用闭包函数(也叫匿名函数)实现 例如: 在上篇博客vue安装使用最后的成绩表练习中的过滤功能的实现: &l ...

  4. Web版需求征集系统所得2,servlet中request.getParameter获值乱码问题解决

    servlet获值乱码问题解决 解决办法一(最简单有效) request.setCharacterEncoding("utf-8"); 解决办法二 因为乱码问题的产生是因为默认格式 ...

  5. 四、蛋炒饭(Egg fried rice)

    蛋炒饭,是一种常见菜肴.最早的记载见于1972年湖南长沙马王堆汉墓出土的竹简上有关"卵火高"的资料.经专家考证,"卵熇"是一种用黏米饭加鸡蛋制成的食品.有人推断 ...

  6. 【CSS 第五天】背景,边框

    总结一下今天所学习的内容,如下: 背景 属性 例子或作用 background background: #00FF00 url(bgimage.gif) no-repeat fixed top; ba ...

  7. Linux下CenOS系统 安装MariaDB

    1.首先去MariaDB官网下载安装包,首页是:https://mariadb.org/ 2.放在linux下的新建目录下:/root/mariadb 然后解压缩,命令为:tar -xzvf mari ...

  8. 长沙学院APP之校园模块设计

    一.简单回顾 在上次的scrum冲刺中,我将整个长沙学院的APP做了一个基本的架构设计以及框架设计,确定好了APP的功能结构以及实现时所要达到的效果,并且做了一个简单的用户登录界面,由于所学知识有限, ...

  9. .NET Core跨平台的奥秘[中篇]:复用之殇

    在<.NET Core跨平台的奥秘[上篇]:历史的枷锁>中我们谈到:由于.NET是建立在CLI这一标准的规范之上,所以它天生就具有了"跨平台"的基因.在微软发布了第一个 ...

  10. [Swift]LeetCode127. 单词接龙 | Word Ladder

    Given two words (beginWord and endWord), and a dictionary's word list, find the length of shortest t ...