async/await学习笔记

async/await特性

异步方法

  • 包含async修饰符

    • 该修饰符只用于标示这个方法有await表达式
  • 至少包含一个await表达式
  • 返回类型必须为下面这三种
    • void//尽量别用
    • Task
    • Task<T>

      Task类代表这次的异步任务,能从Task中获得任务状态,Task用于表示会返回T类型值的任务

  • 参数不能有out,ref
  • 命名约定:以Async结尾

异步方法的控制流

  1. //调用方法
  2. static void Main(string[] args)
  3. {
  4. Console.WriteLine("主方法开始");
  5. Task<int> result= GetIntResult();
  6. Console.WriteLine(" 主方法开始画圈圈");
  7. for (int i = 0; i < 100; i++)
  8. {
  9. Console.Write("○");
  10. }
  11. Console.WriteLine("\n 主方法画圈圈结束");
  12. Console.WriteLine("开始判断异步方法是否完成");
  13. if (!result.IsCompleted)
  14. {
  15. Console.WriteLine("异步方法未完成,开始等待");
  16. result.Wait();
  17. }
  18. else
  19. {
  20. Console.WriteLine("异步方法为完成");
  21. }
  22. Console.WriteLine(" 最终结果:{0}",result.Result);
  23. Console.WriteLine("主方法结束");
  24. Console.ReadKey();
  25. }
  26. //异步方法
  27. public static async Task<int> GetIntResult()
  28. {
  29. Console.WriteLine(" 异步方法开始调用");
  30. int result=await Task<int>.Run<int>(() =>
  31. {
  32. Console.WriteLine(" await异步操作开始,开始计算0到10的和");
  33. int r = 0;
  34. for (int i = 0; i < 10;i++ )
  35. {
  36. r += i;
  37. Thread.Sleep(1000);
  38. }
  39. Console.WriteLine(" await异步操作结束");
  40. return r;
  41. });
  42. Console.WriteLine(" 异步方法调用结束");
  43. return result;
  44. }

输出

  1. 主方法开始
  2. 异步方法开始调用
  3. await异步操作开始,开始计算0到10的和
  4. 主方法开始画圈圈
  5. ○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○
  6. 主方法画圈圈结束
  7. 开始判断异步方法是否完成
  8. 异步方法未完成,开始等待
  9. await异步操作结束
  10. 异步方法调用结束
  11. 最终结果:45
  12. 主方法结束

在这个例子中有几点要提一下

  • 当异步方法执行到第一个await表达式,判断是否完成,如果完成则获得结果否则将控制流交给调用方法.

    在这个例子中36行遇到了await,显然是没完成的,所以跳回第5行继续执行.

  • await和async并不创建新线程,线程的创建都是程序员自己干的,比如该例中第36行是我手动创的一个Task.
  • 还有就是Task在创建的时候就开始执行了

下图取自<<C#图解教程>>

再来个例子

该图取自

https://msdn.microsoft.com/zh-cn/library/hh191443.aspx

await表达式

通常形式是这样的

  1. await Task类型

从上面两张图可以看出await等待Task的结果,如果Task并未完成,将控制流转移给调用者,同时开一个线程异步地执行Task的任务,以及async方法剩下的任务.

  1. static void Main(string[] args)
  2. {
  3. ShowCurrentThreadId("主方法");
  4. var task = Get1To10();
  5. Console.WriteLine("等待异步任务");
  6. task.Wait();
  7. Console.WriteLine("任务完成,结果为" + task.Result);
  8. Console.Read();
  9. }
  10. public static async Task<int> Get1To10()
  11. {
  12. ShowCurrentThreadId("Get1To10开头");
  13. var task1 = await Task<int>.Run<int>(() =>
  14. {
  15. ShowCurrentThreadId("Get1To10的task1任务内部");
  16. int sum = 0;
  17. for (int i = 0; i < 10; i++)
  18. {
  19. sum += i;
  20. Thread.Sleep(100);
  21. }
  22. return sum;
  23. });
  24. ShowCurrentThreadId("Get1To10结尾");
  25. return task1;
  26. }
  27. private static void ShowCurrentThreadId(string msg = "__")
  28. {
  29. Console.WriteLine("这里是{0},当前线程Id为:{1}", msg, Thread.CurrentThread.ManagedThreadId);
  30. }

结果

这里是主方法,当前线程Id为:8

这里是Get1To10开头,当前线程Id为:8

这里是Get1To10的task1任务内部,当前线程Id为:9

等待异步任务

这里是Get1To10结尾,当前线程Id为:9

任务完成,结果为45

这里发现Get1To10剩余部分的线程变成了与task1一样的线程

Take类型有GetAwaiter 方法,返回TaskAwaiter

TaskAwaiter类有成员:

  1. //获取一个值,该值指示异步任务是否已完成。
  2. public bool IsCompleted { get; }
  3. //异步任务完成后关闭等待任务。
  4. public TResult GetResult();
  5. //将操作设置为当 System.Runtime.CompilerServices.TaskAwaiter<TResult> 对象停止等待异步任务完成时执行。
  6. public void OnCompleted(Action continuation);
  7. //计划与此 awaiter 相关异步任务的延续操作。
  8. public void UnsafeOnCompleted(Action continuation);

可以用TaskAwaiter的GetResult()等待结果,其效果等效于await

但通常只用Task类的成员就够了,里面也有类似功能的成员

Net4.5,微软修改了大量的基础类,使得很多类方法能返回Task类

比如

  1. WebClient.DownloadStringTaskAsyn

可以使用Task.Run方法快速创建Task类

Run方法是在另一个线程执行的

相关重载

  1. public static Task Run(Action action);
  2. public static Task<TResult> Run<TResult>(Func<Task<TResult>> function);
  3. public static Task Run(Func<Task> function);
  4. public static Task<TResult> Run<TResult>(Func<TResult> function);
  5. public static Task Run(Action action, CancellationToken cancellationToken);
  6. public static Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken);
  7. public static Task Run(Func<Task> function, CancellationToken cancellationToken);
  8. public static Task<TResult> Run<TResult>(Func<TResult> function, CancellationToken cancellationToken);

取消异步

需要用到CancellationTokeSourceCancellationToken

CancellationTokeSource类有个成员是CancellationToken类型的,CancellationToken的成员IsCancellationRequested用于标记是否取消的

用法

  1. class Program
  2. {
  3. static void Main()
  4. {
  5. //创建Token
  6. CancellationTokenSource cts = new CancellationTokenSource();
  7. CancellationToken token = cts.Token;
  8. MyClass mc = new MyClass();
  9. Task t = mc.RunAsync( token ); //带着token异步运行
  10. //Thread.Sleep( 3000 ); // Wait 3 seconds.
  11. //cts.Cancel(); //调用后将修改IsCancellationRequested标记
  12. t.Wait();
  13. Console.WriteLine( "Was Cancelled: {0}", token.IsCancellationRequested );
  14. }
  15. }
  16. class MyClass
  17. {
  18. public async Task RunAsync( CancellationToken ct )
  19. {
  20. //检查Token是否被取消
  21. if ( ct.IsCancellationRequested )
  22. return;
  23. await Task.Run( () => CycleMethod( ct ), ct );
  24. }
  25. void CycleMethod( CancellationToken ct )
  26. {
  27. Console.WriteLine( "开始方法" );
  28. const int max = 5;
  29. for ( int i=0; i < max; i++ )
  30. {
  31. //检查Token是否被取消
  32. if ( ct.IsCancellationRequested )
  33. return;
  34. Thread.Sleep( 1000 );
  35. Console.WriteLine( " {0} of {1} iterations completed", i + 1, max );
  36. }
  37. }
  38. }

async/await杂谈

Asp.Net中异步编程有什么用,在什么时候用

第一个问题的答案显然是为了提升性能.

经过我查阅各种资料得出的结论是.

在IIS使用有限的线程池服务用户的web请求,在非异步的情况下,一个线程负责一个请求直到请求完成.在这个请求中可能会遇到高IO(读写数据库,保存文件….)或调用别人的web service.这两个工作基本没CPU什么事,在同步调用中线程只能傻傻地等待.这显然是不合理的,当高并发请求时,IIS线程池中的线程都不够用了,却还有一堆的线程在等待.为何不让线程从等待中释放?

基本过程是这样的:

用户请求->执行代码->遇到高IO/WebServer需要线程等待的事->把当前线程放回线程池->当高IO/WebServer完成重新到线程池中拿一个线程完成后续工作

所以异步编程对web的性能提升在一次请求中你是看出来的,可能要用压力测试才能发现.

下面我要开始废话了

在开始学异步的时候我非常迫切的想知道async,await对MVC的作用是什么

  1. public async Task<ViewResult> Index()
  2. {
  3. var result =await DoSometing()
  4. return View(result);
  5. }

这种控制器为啥会提升性能

那时候以为在第3行会异步执行,预想的效果是,先给用户看的一个大的框架,可能有些地方是空的,过一段时间后异步调用完成就给用户显示完整的视图.

显然我是错的,我上述的描述明显应该是用ajax异步调用生成的,关MVC鸟事.

后来在群友交流中发现的一种情况,假设一个大的视图中是多个小视图的执行结果(@HTML.Action)拼凑起来,那么在小视图生成的时候是不是异步执行的?

那位群友尝试过,他说在MVC5会返回HttpServerUtility.Execute 在等待异步操作完成时被阻止。错误,但是这个在MVC6中改善,但我没去试验.

什么是SynchronizationContext

简单的介绍这个东西,为下文铺垫

SynchronizationContext就是允许一个线程和另外一个线程进行通讯,SynchronizationContext在通讯中充当传输者的角色。另外这里有个地方需要清楚的,不是每个线程都附加SynchronizationContext这个对象,只有UI线程是一直拥有的,因为在控件(Control)实例化的时候都会把SynchronizationContext对象放到这个线程里

但我们把该对象从UI线程传递到第二个线程时候,即可利用SynchronizationContext的两个方法,调用某个方法,而这个方法调用时候却用的是UI线程

  1. //
  2. // 摘要:
  3. // 当在派生类中重写时,将异步消息调度到一个同步上下文。
  4. //
  5. // 参数:
  6. // d:
  7. // 要调用的 System.Threading.SendOrPostCallback 委托。
  8. //
  9. // state:
  10. // 传递给委托的对象。
  11. public virtual void Post(SendOrPostCallback d, object state);
  12. //
  13. // 摘要:
  14. // 当在派生类中重写时,将一个同步消息调度到一个同步上下文。
  15. //
  16. // 参数:
  17. // d:
  18. // 要调用的 System.Threading.SendOrPostCallback 委托。
  19. //
  20. // state:
  21. // 传递给委托的对象。
  22. //
  23. // 异常:
  24. // System.NotSupportedException:
  25. // 在 Windows Store 应用程序中调用的方法。用于 Windows Store 应用程序的 System.Threading.SynchronizationContext
  26. // 的实现应用不支持 System.Threading.SynchronizationContext.Send(System.Threading.SendOrPostCallback,System.Object)
  27. // 方法。
  28. public virtual void Send(SendOrPostCallback d, object state);

其中SendOrPostCallback是一个委托

  1. // 摘要:
  2. // 表示在消息即将被调度到同步上下文时要调用的方法。
  3. //
  4. // 参数:
  5. // state:
  6. // 传递给委托的对象。
  7. public delegate void SendOrPostCallback(object state);

state是额外的数据,想传什么就传什么

使用案例

  1. public partial class Form1 : Form
  2. {
  3. public Form1()
  4. {
  5. InitializeComponent();
  6. }
  7. private void mToolStripButtonThreads_Click(object sender, EventArgs e)
  8. {
  9. //获得当前线程的SynchronizationContext,因为是UI线程所以必定不为null
  10. SynchronizationContext uiContext = SynchronizationContext.Current;
  11. //创建新线程
  12. Thread thread = new Thread(Run);
  13. //开始运行线程,传入SynchronizationContext,新线程能利用它来更新UI线程
  14. thread.Start(uiContext);
  15. }
  16. //新线程调用的方法
  17. private void Run(object state)
  18. {
  19. // 获得UI线程的SynchronizationContext
  20. SynchronizationContext uiContext = state as SynchronizationContext;
  21. for (int i = 0; i < 1000; i++)
  22. {
  23. //假设是某些耗时操作
  24. Thread.Sleep(10);
  25. //异步的方式让UI线程执行UpataUI方法,
  26. uiContext.Post(UpdateUI, "line " + i.ToString());
  27. }
  28. }
  29. /// <summary>
  30. /// 该方法是被UI线程执行的
  31. /// </summary>
  32. private void UpdateUI(object state)
  33. {
  34. string text = state as string;
  35. mListBox.Items.Add(text);
  36. }
  37. }

在Asp.Net中

通过输出SynchronizationContext.Current.ToString()可知,web中的SynchronizationContext是AspNetSynchronizationContext

async、await 线程死锁问题

  1. public ActionResult Asv2()
  2. {
  3. var task = AssignValue2();
  4. task.Wait();//dead lock
  5. return Content(_container);
  6. }
  7. private void Assign()
  8. {
  9. _container = "Hello World";
  10. }
  11. public async Task AssignValue2()
  12. {
  13. await Task.Delay(500);
  14. //dead lock
  15. await Task.Run(() => Assign());
  16. }

这小段代码搬运自,我也懒得写,我也不会用自己另外写一份差不多的代码会让本文更加的有”原创性”

ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法

解析下这段代码

第3行开始调用async代码

第15行开始等待,控制流返回第3行

第4行,等待Task.Delay(500)完成,主线程陷入阻塞.

第15行,Task.Delay(500)完成,重点来了,默认情况下Task完成后会尝试获得SynchronizationContext,在Asp.Net也就是AspNetSynchronizationContext.这个东西属于主线程,此时主线程为了等待Task.Delay完成而阻塞了,所以两者互相等待,于是死锁了.

解决办法

  • 第4行不用Wait(),改用await,这样就不阻塞主线程
  • 使用.ConfigureAwait(false),也就是在第3行改为AssignValue2.ConfigureAwait(false)
  1. public ConfiguredTaskAwaitable ConfigureAwait(
  2. bool continueOnCapturedContext
  3. )
  4. continueOnCapturedContext
  5. 类型: System.Boolean
  6. 尝试将延续任务封送回原始上下文,则为 true;否则为 false。
  7. 默认为true

上面说到Task完成后会尝试获得SynchronizationContext,这个是死锁的原因之一,我们可以用ConfigureAwait打破这个条件,让Task不去获得SynchronizationContext.

在有些文章中写道要多用ConfigureAwait(false),因为他们认为尝试去获取原始线程的上下文是耗费性能的.

但这也可能会出现一些问题

  1. public async Task<ActionResult> Index()
  2. {
  3. string VALUE = await GETVALUE().ConfigureAwait(false);
  4. bool HttpContextIsNull = System.Web.HttpContext.Current == null ? true : false;
  5. bool SynchronizationContextIsNull = SynchronizationContext.Current == null ? true : false;
  6. return View((object)string.Format("HttpContext是否为NULL:{0},SynchronizationContext是否为NULL{1}", HttpContextIsNull, SynchronizationContextIsNull));
  7. }
  8. private async Task<string> GETVALUE()
  9. {
  10. await Task.Run(() => Thread.Sleep(3000)).ConfigureAwait(false);
  11. return "string";
  12. }

结果是HttpContext,SynchronizationContext都为NULL.可怕吧

反过来说,如果不设置为NULL,Task完成后会尝试获得之前的上下文,你可以自己写些代码测试,你会发现虽然await前后代码线程Id不一样,但是System.Web.HttpContext.Current对象是一样的(Object.GetHashCode())

资料

async和await的前世今生

异步编程 In .NET

异步编程中的最佳做法

走进异步世界-犯傻也值得分享:ConfigureAwait(false)使用经验分享

async、await在ASP.NET[ MVC]中之线程死锁的故事

[你必须知道的异步编程]C# 5.0 新特性——Async和Await使异步编程更简单

HttpServerUtility.Execute 在等待异步操作完成时被阻止。关键词:MVC,分部视图,异步

来自为知笔记(Wiz)

时间: 2024-10-25 09:44:51

async/await学习笔记的相关文章

async 函数--学习笔记一

含义: ES2017 标准引入了 async 函数,使得异步操作变得更加方便.async 函数是什么?一句话,它就是 Generator 函数的语法糖. 前文有一个 Generator 函数,依次读取两个文件. var fs = require('fs'); var readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(erro

多线程编程学习笔记——async和await(二)

接上文 多线程编程学习笔记——async和await(一) 三.   对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步调用 . 1.示例代码如下. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Thread

多线程编程学习笔记——async和await(三)

接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多个并行的异步操作使用await时聚合异常. 1.程序示例代码如下. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

Koa2学习(二)async/await

Koa2学习(二)async/await koa2中用到了大量的async/await语法,要学习koa2框架,首先要好好理解async/await语法. async/await顾名思义是一个异步等待的语法,是es7中为了实现用同步的方式写异步方法的一种新式语法. async 我们再来看看async到底是一个什么语法: 普通的方法: function syncF() { return 'I am a sync result' } let sync_result = syncF() console

JS学习- ES6 async await使用

async 函数是什么?一句话,它就是 Generator 函数的语法糖. 使用场景常常会遇到,请求完一个接口,拿完值再去请求另外一个接口,我们之前回调callback函数处理,如果很多的情况下,看起来很冗余,这时我们可以用async函数. 比如我们有两个请求,如下,这里用的axios: function getCode(){ return axios.get('json/code.json'); } function getlist(params){ return axios.get('jso

js异步回调Async/Await与Promise区别 新学习使用Async/Await

Promise,我们了解到promise是ES6为解决异步回调而生,避免出现这种回调地狱,那么为何又需要Async/Await呢?你是不是和我一样对Async/Await感兴趣以及想知道如何使用,下面一起来看看这篇文章:Async/Await替代Promise的6个理由. 什么是Async/Await? async/await是写异步代码的新方式,以前的方法有回调函数和Promise. async/await是基于Promise实现的,它不能用于普通的回调函数. async/await与Prom

.net 异步编程async &amp; await关键字的思考

C# 5.0引入了两个关键字 async和await,这两个关键字在很大程度上帮助我们简化了异步编程的实现代码,而且TPL中的task与async和await有很大的关系 思考了一下异步编程中的async & await关键字,对两个关键字尤其是await关键字一直很迷糊,因此深入思考了一下.首先借助的示例是:[你必须知道的异步编程]C# 5.0 新特性--Async和Await使异步编程更简单这是博客园一个大牛写的,自己也一直关注这个大神,不得不说,博客园大神很多,而且氛围也很好.我引入了其中

thrift学习笔记

Thrift学习笔记 一:thrift介绍 Thrift是facebook开发的用来处理各不同系统之间数据通讯的rpc服务框架,后来成为apche的开源项目.thrift支持多种程序语言,包括Java,Python,Ruby,JavaScript,Node.js,Go,C,C++,C#,Erlang,Delphi,Perl,Php,SmallTalk,OCaml,Haxe,Haskell,D语言.Thrift采用IDL(Interface Defination Language)描述性语言来定义

Asp.Net Identity学习笔记+MVC5默认项目解析_第三方登入&授权总结

Identity学习笔记 Asp.Net Identity学习笔记+MVC5默认项目解析_基础用法 Asp.Net Identity学习笔记+MVC5默认项目解析_授权&Claim Asp.Net Identity学习笔记+MVC5默认项目解析_第三方登入&授权总结 Identity学习笔记第三方登入配置登入案例登入技术总结本地,已登入本地,未登入第三方登入 第三方登入 本文介绍Identity的第三方登入技术.到目前为止只介绍了CookieAuthentication这种授权方式,即浏览