一、异步方法返回类型
只能返回3种类型(void、Task和Task<T>)。
1.1、void返回类型:调用方法执行异步方法,但又不需要做进一步的交互。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program { static void Main(string[] args) { #region async & await入门二之void返回类型 AddAsync(1, 2); Thread.Sleep(1000); Console.WriteLine("AddAsync方法执行完成。"); Console.Read(); #endregion } /// <summary> /// 加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static int Add(int n, int m) { return n + m; } /// <summary> /// 异步加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> private static async void AddAsync(int n, int m) { int val = await Task.Run(() => Add(n, m)); Console.WriteLine($"Result: {val}"); } }
运行结果如下:
1.2、Task返回类型:调用方法不需要从异步方法中取返回值,但是希望检查异步方法的状态,那么可以选择可以返回Task类型的对象。不过,就算异步方法中包含
return语句,也不会返回任何东西。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program { static void Main(string[] args) { #region async & await入门二之Task返回类型 Task task = TaskAddAsync(1, 2); task.Wait(); Console.WriteLine("TaskAddAsync方法执行完成。"); Console.Read(); #endregion } /// <summary> /// 加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static int Add(int n, int m) { return n + m; } /// <summary> /// 异步加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static async Task TaskAddAsync(int n, int m) { int val = await Task.Run(() => Add(n, m)); Console.WriteLine($"Result: {val}"); } }
运行结果如下:
1.3、Task<T>返回类型:调用方法要从调用中获取一个T类型的值,异步方法的返回类型就必须是Task<T>。调用方法从Task的Result属性获取的就是T类型的值。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program { static void Main(string[] args) { #region async & await入门二之Task<T>返回类型 Task<int> task = TaskTAddAsync(1, 2); task.Wait(); Console.WriteLine($"Result: {task.Result}"); Console.Read(); #endregion } /// <summary> /// 加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static int Add(int n, int m) { return n + m; } /// <summary> /// 异步加法 /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <returns></returns> private static async Task<int> TaskTAddAsync(int n, int m) { int val = await Task.Run(() => Add(n, m)); return val; } }
运行结果如下:
![](/qrcode.jpg)
二、异步方法控制流
异步方法的控制流:
1)异步执行await表达式的空闲任务。
2)await表达式执行完毕,继续执行表达式后续部分。如再遇到await表达式,按相同情况进行处理。
3)到达末尾或遇到return语句时,根据返回类型可以分三种情况:
<1>void:退出控制流。
<2>Task:设置Task的属性并退出。
<3>Task<T>:设置Task的属性和返回值(Result属性)并退出。
4)调用方法将继续执行。需要注意的是:若调用方法需要用到异步方法结果的时候,会暂停等到Task对象的Result属性被赋值后才会继续执行。
【难点】
1)第一次遇到await所返回对象的类型,是同步方法头的返回类型,跟await表达式的返回值没有关系。
2)到达异步方法的末尾或遇到return语句,它并没有真正的返回一个值,而是退出了该方法。
三、异步方法await表达式
在大多数的时候,await一般和Task一起使用,用await表达式来指定一个异步执行的任务,以实现更高的灵活性和效率。
可以用于await运算符的对象要求如下:
1)有一个GetAwaiter()方法或扩展方法,它返回一个实现了INotifyCompletion接口的awaiter对象(或结构)
2)返回的awaiter对象(或结构)要求实现如下方法:
<1>void OnCompleted(Action continuation)
<2>bool IsCompleted { get ; }
<3>TResult GetResult() //TResult也可以是void类型
下面简单的介绍一下await运算符是如何实现异步操作的?
例如,对于如下代码
var j = await 3;
DoContinue(j);
在编译的时候会被编译器翻译为类似如下流程的代码(注:这个只是功能类似的简化流程示例,实际并非如此)。
var awaiter = 3.GetAwaiter();
var continuation = new Action(() =>
{
var j = awaiter.GetResult();
DoContinue(j);
});
if (awaiter.IsCompleted)
continuation();
else
awaiter.OnCompleted(continuation);
有了这个基础,我们就可以对一个int型的变量实现await操作了:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/// <summary> /// MyAwaiter类 /// </summary> class MyAwaiter : System.Runtime.CompilerServices.INotifyCompletion { public bool IsCompleted { get { return false; } } public void OnCompleted(Action continuation) { Console.WriteLine("OnCompleted"); ThreadPool.QueueUserWorkItem(_ => { Thread.Sleep(1000); result = 300; continuation(); }); } int result; public int GetResult() { Console.WriteLine("GetResult"); return result; } } /// <summary> /// MyAwaiter类扩展 /// </summary> static class MyAwaiterExtend { public static MyAwaiter GetAwaiter(this int i) { return new MyAwaiter(); } } class Program { static void Main(string[] args) { #region #region async & await入门二之await如何实现异步 AwaitAchieveAsync(); Console.Read(); #endregion } /// <summary> /// AwaitAchieveAsync异步方法 /// </summary> public static async void AwaitAchieveAsync() { var j = await 3; Console.WriteLine(j); } }
运行结果如下:
这样我们就能看出await是如何实现call/cc式的异步操作了:
1)编译器把后续操作封装为一个Action对象continuation,传入awaiter的OnCompleted函数并执行。
2)awaiter在OnCompleted函数中执行异步操作,并在异步操作完成后(一般是异步调用的回调函数)执行continuation操作。
3)continuation操作的第一步就是调用awaiter.GetResult()获取异步操作的返回值,并继续执行后续操作。
看到这里,相信大家对await的机制已经有了简单的认识,也就不难理解为什么AsyncTargetingPack能使得.NET 4.0程序也支持await操作了——该库在
AsyncCompatLibExtensions类中对Task类提供了GetAwaiter扩展函数而已。
以上是通过创建自己的awaitable类型来演示await实现异步的过程,实际上,你并不需要构建自己的awaitable类型,只需要使用Task类即可。每一个任务都是awaitable
类的实例。
下面代码演示使用Task.Run()来创建一个Task。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program { static void Main(string[] args) { #region async & await入门二之使用Task.Run创建Task var task = GetGuidAsync(); task.Wait(); Console.Read(); #endregion } /// <summary> /// 获取 Guid /// </summary> /// <returns></returns> private static Guid GetGuid() { return Guid.NewGuid(); } /// <summary> /// 异步获取Guid /// </summary> /// <returns></returns> private static async Task GetGuidAsync() { var myFunc = new Func<Guid>(GetGuid); var t1 = await Task.Run(myFunc); var t2 = await Task.Run(new Func<Guid>(GetGuid)); var t3 = await Task.Run(() => GetGuid()); var t4 = await Task.Run(() => Guid.NewGuid()); Console.WriteLine($"t1: {t1}"); Console.WriteLine($"t2: {t2}"); Console.WriteLine($"t3: {t3}"); Console.WriteLine($"t4: {t4}"); } }
上面4个Task.Run() 都是采用了Task.Run(Func<TResult> func) 形式来直接或间接调用Guid.NewGuid()。
运行结果如下:
Task.Run()支持4种不同的委托类型:Action、Func<TResult>、Func<Task> 和 Func<Task<TResult>>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program { static void Main(string[] args) { #region async & await入门二之使用Task.Run支持的4种委托类型 var task = GetGuidFrom4Async(); task.Wait(); Console.Read(); #endregion } /// <summary> /// 获取 Guid /// </summary> /// <returns></returns> private static Guid GetGuid() { return Guid.NewGuid(); } /// <summary> /// 异步获取Guid(Task.Run支持的4种委托类型) /// </summary> /// <returns></returns> private static async Task GetGuidFrom4Async() { await Task.Run(() => { Console.WriteLine(Guid.NewGuid()); }); //Action Console.WriteLine(await Task.Run(() => Guid.NewGuid())); //Func<TResult> await Task.Run(() => Task.Run(() => { Console.WriteLine(Guid.NewGuid()); })); //Func<Task> Console.WriteLine(await Task.Run(() => Task.Run(() => Guid.NewGuid()))); //Func<Task<TResult>> }
运行结果如下:
四、异步方法取消
CancellationToken和CancellationTokenSource这两个类允许你终止执行异步方法。
1)CancellationToken对象包含任务是否被取消的信息。如果该对象的属性IsCancellationRequested为true,任务需停止操作并返回。该对象操作是不可逆的,且只能
使用(修改)一次,即该对象内的IsCancellationRequested属性被设置后,就不能改动。
2)CancellationTokenSource可创建CancellationToken对象,调用CancellationTokenSource对象的Cancel方法,会使该对象的CancellationToken属性
IsCancellationRequested设置为true。
【注意】调用CancellationTokenSource对象的Cancel方法,并不会执行取消操作,而是会将该对象的CancellationToken属性IsCancellationRequested设置为true。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Program { static void Main(string[] args) { #region async & await入门二之异步方法取消 CancellationTokenSource source = new CancellationTokenSource(); CancellationToken token = source.Token; var task = ExecuteAsync(token); Console.WriteLine($"{nameof(token.IsCancellationRequested)}: {token.IsCancellationRequested}"); Thread.Sleep(6000); //挂起6秒 source.Cancel(); //传达取消请求 task.Wait(token); //等待任务执行完成 Console.WriteLine($"{nameof(token.IsCancellationRequested)}: {token.IsCancellationRequested}"); Console.Read(); #endregion } /// <summary> /// 异步执行 /// </summary> /// <param name="token"></param> /// <returns></returns> private static async Task ExecuteAsync(CancellationToken token) { if (token.IsCancellationRequested) { return; } await Task.Run(() => CircleOutput(token), token); } /// <summary> /// 循环输出 /// </summary> /// <param name="token"></param> private static void CircleOutput(CancellationToken token) { Console.WriteLine($"{nameof(CircleOutput)} 方法开始调用:"); const int num = 5; for (var i = 0; i < num; i++) { if (token.IsCancellationRequested) //监控CancellationToken { return; } Console.WriteLine($"{i + 1}/{num} 完成"); Thread.Sleep(1000); } } }
运行结果如下:
参考自:
https://www.cnblogs.com/liqingwen/p/5844095.html
https://www.cnblogs.com/TianFang/archive/2012/09/21/2696769.html