标签:
async修饰符
await表达式返回类型必须为下面这三种
Task<T>
Task类代表这次的异步任务,能从Task中获得任务状态,Task
用于表示会返回T类型值的任务
参数不能有out,ref
//调用方法static void Main(string[] args){Console.WriteLine("主方法开始");Task<int> result= GetIntResult();Console.WriteLine(" 主方法开始画圈圈");for (int i = 0; i < 100; i++){Console.Write("○");}Console.WriteLine("\n 主方法画圈圈结束");Console.WriteLine("开始判断异步方法是否完成");if (!result.IsCompleted){Console.WriteLine("异步方法未完成,开始等待");result.Wait();}else{Console.WriteLine("异步方法为完成");}Console.WriteLine(" 最终结果:{0}",result.Result);Console.WriteLine("主方法结束");Console.ReadKey();}//异步方法public static async Task<int> GetIntResult(){Console.WriteLine(" 异步方法开始调用");int result=await Task<int>.Run<int>(() =>{Console.WriteLine(" await异步操作开始,开始计算0到10的和");int r = 0;for (int i = 0; i < 10;i++ ){r += i;Thread.Sleep(1000);}Console.WriteLine(" await异步操作结束");return r;});Console.WriteLine(" 异步方法调用结束");return result;}
输出
主方法开始异步方法开始调用await异步操作开始,开始计算0到10的和主方法开始画圈圈○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○○主方法画圈圈结束开始判断异步方法是否完成异步方法未完成,开始等待await异步操作结束异步方法调用结束最终结果:45主方法结束
在这个例子中有几点要提一下
await表达式,判断是否完成,如果完成则获得结果否则将控制流交给调用方法. 下图取自<<C#图解教程>>

再来个例子

该图取自
https://msdn.microsoft.com/zh-cn/library/hh191443.aspx
通常形式是这样的
await Task类型
从上面两张图可以看出await等待Task的结果,如果Task并未完成,将控制流转移给调用者,同时开一个线程异步地执行Task的任务,以及async方法剩下的任务.
static void Main(string[] args){ShowCurrentThreadId("主方法");var task = Get1To10();Console.WriteLine("等待异步任务");task.Wait();Console.WriteLine("任务完成,结果为" + task.Result);Console.Read();}public static async Task<int> Get1To10(){ShowCurrentThreadId("Get1To10开头");var task1 = await Task<int>.Run<int>(() =>{ShowCurrentThreadId("Get1To10的task1任务内部");int sum = 0;for (int i = 0; i < 10; i++){sum += i;Thread.Sleep(100);}return sum;});ShowCurrentThreadId("Get1To10结尾");return task1;}private static void ShowCurrentThreadId(string msg = "__"){Console.WriteLine("这里是{0},当前线程Id为:{1}", msg, Thread.CurrentThread.ManagedThreadId);}
结果
这里是主方法,当前线程Id为:8
这里是Get1To10开头,当前线程Id为:8
这里是Get1To10的task1任务内部,当前线程Id为:9
等待异步任务
这里是Get1To10结尾,当前线程Id为:9
任务完成,结果为45
这里发现Get1To10剩余部分的线程变成了与task1一样的线程
Take类型有GetAwaiter 方法,返回TaskAwaiter
TaskAwaiter类有成员:
//获取一个值,该值指示异步任务是否已完成。public bool IsCompleted { get; }//异步任务完成后关闭等待任务。public TResult GetResult();//将操作设置为当 System.Runtime.CompilerServices.TaskAwaiter<TResult> 对象停止等待异步任务完成时执行。public void OnCompleted(Action continuation);//计划与此 awaiter 相关异步任务的延续操作。public void UnsafeOnCompleted(Action continuation);
可以用TaskAwaiter的GetResult()等待结果,其效果等效于await
但通常只用Task类的成员就够了,里面也有类似功能的成员
Net4.5,微软修改了大量的基础类,使得很多类方法能返回Task类
比如
WebClient.DownloadStringTaskAsyn
可以使用Task.Run方法快速创建Task类
Run方法是在另一个线程执行的
相关重载
public static Task Run(Action action);public static Task<TResult> Run<TResult>(Func<Task<TResult>> function);public static Task Run(Func<Task> function);public static Task<TResult> Run<TResult>(Func<TResult> function);public static Task Run(Action action, CancellationToken cancellationToken);public static Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken);public static Task Run(Func<Task> function, CancellationToken cancellationToken);public static Task<TResult> Run<TResult>(Func<TResult> function, CancellationToken cancellationToken);
需要用到CancellationTokeSource和CancellationToken
CancellationTokeSource类有个成员是CancellationToken类型的,CancellationToken的成员IsCancellationRequested用于标记是否取消的
用法
class Program{static void Main(){//创建TokenCancellationTokenSource cts = new CancellationTokenSource();CancellationToken token = cts.Token;MyClass mc = new MyClass();Task t = mc.RunAsync( token ); //带着token异步运行//Thread.Sleep( 3000 ); // Wait 3 seconds.//cts.Cancel(); //调用后将修改IsCancellationRequested标记t.Wait();Console.WriteLine( "Was Cancelled: {0}", token.IsCancellationRequested );}}class MyClass{public async Task RunAsync( CancellationToken ct ){//检查Token是否被取消if ( ct.IsCancellationRequested )return;await Task.Run( () => CycleMethod( ct ), ct );}void CycleMethod( CancellationToken ct ){Console.WriteLine( "开始方法" );const int max = 5;for ( int i=0; i < max; i++ ){//检查Token是否被取消if ( ct.IsCancellationRequested )return;Thread.Sleep( 1000 );Console.WriteLine( " {0} of {1} iterations completed", i + 1, max );}}}
第一个问题的答案显然是为了提升性能.
经过我查阅各种资料得出的结论是.
在IIS使用有限的线程池服务用户的web请求,在非异步的情况下,一个线程负责一个请求直到请求完成.在这个请求中可能会遇到高IO(读写数据库,保存文件….)或调用别人的web service.这两个工作基本没CPU什么事,在同步调用中线程只能傻傻地等待.这显然是不合理的,当高并发请求时,IIS线程池中的线程都不够用了,却还有一堆的线程在等待.为何不让线程从等待中释放?
基本过程是这样的:
用户请求->执行代码->遇到高IO/WebServer需要线程等待的事->把当前线程放回线程池->当高IO/WebServer完成重新到线程池中拿一个线程完成后续工作
所以异步编程对web的性能提升在一次请求中你是看出来的,可能要用压力测试才能发现.
下面我要开始废话了
在开始学异步的时候我非常迫切的想知道async,await对MVC的作用是什么
public async Task<ViewResult> Index(){var result =await DoSometing()return View(result);}
这种控制器为啥会提升性能
那时候以为在第3行会异步执行,预想的效果是,先给用户看的一个大的框架,可能有些地方是空的,过一段时间后异步调用完成就给用户显示完整的视图.
显然我是错的,我上述的描述明显应该是用ajax异步调用生成的,关MVC鸟事.
后来在群友交流中发现的一种情况,假设一个大的视图中是多个小视图的执行结果(@HTML.Action)拼凑起来,那么在小视图生成的时候是不是异步执行的?
那位群友尝试过,他说在MVC5会返回HttpServerUtility.Execute 在等待异步操作完成时被阻止。错误,但是这个在MVC6中改善,但我没去试验.
简单的介绍这个东西,为下文铺垫
SynchronizationContext就是允许一个线程和另外一个线程进行通讯,SynchronizationContext在通讯中充当传输者的角色。另外这里有个地方需要清楚的,不是每个线程都附加SynchronizationContext这个对象,只有UI线程是一直拥有的,因为在控件(Control)实例化的时候都会把SynchronizationContext对象放到这个线程里
但我们把该对象从UI线程传递到第二个线程时候,即可利用SynchronizationContext的两个方法,调用某个方法,而这个方法调用时候却用的是UI线程
//// 摘要:// 当在派生类中重写时,将异步消息调度到一个同步上下文。//// 参数:// d:// 要调用的 System.Threading.SendOrPostCallback 委托。//// state:// 传递给委托的对象。public virtual void Post(SendOrPostCallback d, object state);//// 摘要:// 当在派生类中重写时,将一个同步消息调度到一个同步上下文。//// 参数:// d:// 要调用的 System.Threading.SendOrPostCallback 委托。//// state:// 传递给委托的对象。//// 异常:// System.NotSupportedException:// 在 Windows Store 应用程序中调用的方法。用于 Windows Store 应用程序的 System.Threading.SynchronizationContext// 的实现应用不支持 System.Threading.SynchronizationContext.Send(System.Threading.SendOrPostCallback,System.Object)// 方法。public virtual void Send(SendOrPostCallback d, object state);
其中SendOrPostCallback是一个委托
// 摘要:// 表示在消息即将被调度到同步上下文时要调用的方法。//// 参数:// state:// 传递给委托的对象。public delegate void SendOrPostCallback(object state);
state是额外的数据,想传什么就传什么
使用案例
public partial class Form1 : Form{public Form1(){InitializeComponent();}private void mToolStripButtonThreads_Click(object sender, EventArgs e){//获得当前线程的SynchronizationContext,因为是UI线程所以必定不为nullSynchronizationContext uiContext = SynchronizationContext.Current;//创建新线程Thread thread = new Thread(Run);//开始运行线程,传入SynchronizationContext,新线程能利用它来更新UI线程thread.Start(uiContext);}//新线程调用的方法private void Run(object state){// 获得UI线程的SynchronizationContextSynchronizationContext uiContext = state as SynchronizationContext;for (int i = 0; i < 1000; i++){//假设是某些耗时操作Thread.Sleep(10);//异步的方式让UI线程执行UpataUI方法,uiContext.Post(UpdateUI, "line " + i.ToString());}}/// <summary>/// 该方法是被UI线程执行的/// </summary>private void UpdateUI(object state){string text = state as string;mListBox.Items.Add(text);}}
在Asp.Net中
通过输出SynchronizationContext.Current.ToString()可知,web中的SynchronizationContext是AspNetSynchronizationContext
public ActionResult Asv2(){var task = AssignValue2();task.Wait();//dead lockreturn Content(_container);}private void Assign(){_container = "Hello World";}public async Task AssignValue2(){await Task.Delay(500);//dead lockawait Task.Run(() => Assign());}
这小段代码搬运自,我也懒得写,我也不会用自己另外写一份差不多的代码会让本文更加的有”原创性”
ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法
解析下这段代码
第3行开始调用async代码
第15行开始等待,控制流返回第3行
第4行,等待Task.Delay(500)完成,主线程陷入阻塞.
第15行,Task.Delay(500)完成,重点来了,默认情况下Task完成后会尝试获得SynchronizationContext,在Asp.Net也就是AspNetSynchronizationContext.这个东西属于主线程,此时主线程为了等待Task.Delay完成而阻塞了,所以两者互相等待,于是死锁了.
解决办法
public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext)continueOnCapturedContext类型: System.Boolean尝试将延续任务封送回原始上下文,则为 true;否则为 false。默认为true
上面说到Task完成后会尝试获得SynchronizationContext,这个是死锁的原因之一,我们可以用ConfigureAwait打破这个条件,让Task不去获得SynchronizationContext.
在有些文章中写道要多用ConfigureAwait(false),因为他们认为尝试去获取原始线程的上下文是耗费性能的.
但这也可能会出现一些问题
public async Task<ActionResult> Index(){string VALUE = await GETVALUE().ConfigureAwait(false);bool HttpContextIsNull = System.Web.HttpContext.Current == null ? true : false;bool SynchronizationContextIsNull = SynchronizationContext.Current == null ? true : false;return View((object)string.Format("HttpContext是否为NULL:{0},SynchronizationContext是否为NULL{1}", HttpContextIsNull, SynchronizationContextIsNull));}private async Task<string> GETVALUE(){await Task.Run(() => Thread.Sleep(3000)).ConfigureAwait(false);return "string";}
结果是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,分部视图,异步
标签:
原文地址:http://www.cnblogs.com/Recoding/p/5739035.html