async/await特性
异步方法
- 包含
async
修饰符
- 该修饰符只用于标示这个方法有await表达式
- 至少包含一个
await
表达式
- 返回类型必须为下面这三种
- void//尽量别用
- Task
Task<T>
Task类代表这次的异步任务,能从Task中获得任务状态,Task用于表示会返回T类型值的任务
- 参数不能有out,ref
- 命名约定:以Async结尾
异步方法的控制流
//调用方法
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;
}
async
修饰符
- 该修饰符只用于标示这个方法有await表达式
await
表达式- void//尽量别用
- Task
Task<T>
Task类代表这次的异步任务,能从Task中获得任务状态,Task用于表示会返回T类型值的任务
//调用方法
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表达式
,判断是否完成,如果完成则获得结果否则将控制流交给调用方法.在这个例子中36行遇到了await,显然是没完成的,所以跳回第5行继续执行.
- await和async并不创建新线程,线程的创建都是程序员自己干的,比如该例中第36行是我手动创的一个Task.
- 还有就是Task在创建的时候就开始执行了
下图取自<<C#图解教程>>
再来个例子
该图取自
https://msdn.microsoft.com/zh-cn/library/hh191443.aspx
await表达式
通常形式是这样的
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()
{
//创建Token
CancellationTokenSource 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 );
}
}
}
async/await杂谈
Asp.Net中异步编程有什么用,在什么时候用
第一个问题的答案显然是为了提升性能.
经过我查阅各种资料得出的结论是.
在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在通讯中充当传输者的角色。另外这里有个地方需要清楚的,不是每个线程都附加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线程所以必定不为null
SynchronizationContext uiContext = SynchronizationContext.Current;
//创建新线程
Thread thread = new Thread(Run);
//开始运行线程,传入SynchronizationContext,新线程能利用它来更新UI线程
thread.Start(uiContext);
}
//新线程调用的方法
private void Run(object state)
{
// 获得UI线程的SynchronizationContext
SynchronizationContext 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
async、await 线程死锁问题
public ActionResult Asv2()
{
var task = AssignValue2();
task.Wait();//dead lock
return Content(_container);
}
private void Assign()
{
_container = "Hello World";
}
public async Task AssignValue2()
{
await Task.Delay(500);
//dead lock
await 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完成而阻塞了,所以两者互相等待,于是死锁了.
解决办法
- 第4行不用Wait(),改用await,这样就不阻塞主线程
- 使用.ConfigureAwait(false),也就是在第3行改为AssignValue2.ConfigureAwait(false)
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())
资料
走进异步世界-犯傻也值得分享:ConfigureAwait(false)使用经验分享
async、await在ASP.NET[ MVC]中之线程死锁的故事
[你必须知道的异步编程]C# 5.0 新特性——Async和Await使异步编程更简单
HttpServerUtility.Execute 在等待异步操作完成时被阻止。关键词:MVC,分部视图,异步