码迷,mamicode.com
首页 > 其他好文 > 详细

async/await学习笔记

时间:2016-08-05 06:27:10      阅读:370      评论:0      收藏:0      [点我收藏+]

标签:

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异步操作开始,开始计算010的和
  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,分部视图,异步





async/await学习笔记

标签:

原文地址:http://www.cnblogs.com/Recoding/p/5739035.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!