C#线程学习笔记九:async & await入门二
一、异步方法返回类型
只能返回3种类型(void、Task和Task<T>)。
1.1、void返回类型:调用方法执行异步方法,但又不需要做进一步的交互。
class Program
{
static void Main(string[] args)
{
#region async & await入门二之void返回类型
AddAsync(, );
Thread.Sleep();
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语句,也不会返回任何东西。
class Program
{
static void Main(string[] args)
{
#region async & await入门二之Task返回类型
Task task = TaskAddAsync(, );
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类型的值。
class Program
{
static void Main(string[] args)
{
#region async & await入门二之Task<T>返回类型
Task<int> task = TaskTAddAsync(, );
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;
}
}
运行结果如下:

二、异步方法控制流

异步方法的控制流:
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操作了:
/// <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();
result = ;
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 async & await入门二之await如何实现异步
AwaitAchieveAsync();
Console.Read();
#endregion
} /// <summary>
/// AwaitAchieveAsync异步方法
/// </summary>
public static async void AwaitAchieveAsync()
{
var j = await ;
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。
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>>
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>>
}
运行结果如下:

四、异步方法暂停
Task.Delay() 与Thread.Sleep不同的是,它不会阻塞线程,意味着线程可以继续处理其它工作。
class Program
{
static void Main(string[] args)
{
#region async & await入门二之异步方法暂停
Console.WriteLine($"{nameof(Main)} start.");
DoAsync();
Console.WriteLine($"{nameof(Main)} end.");
Console.Read();
#endregion
} /// <summary>
/// 异步执行
/// </summary>
private static async void DoAsync()
{
Console.WriteLine($"{nameof(DoAsync)} start.");
await Task.Delay();
Console.WriteLine($"{nameof(DoAsync)} end.");
}
}
运行结果如下:

五、异步方法取消
CancellationToken和CancellationTokenSource这两个类允许你终止执行异步方法。
1)CancellationToken对象包含任务是否被取消的信息。如果该对象的属性IsCancellationRequested为true,任务需停止操作并返回。该对象操作是不可逆的,且只能
使用(修改)一次,即该对象内的IsCancellationRequested属性被设置后,就不能改动。
2)CancellationTokenSource可创建CancellationToken对象,调用CancellationTokenSource对象的Cancel方法,会使该对象的CancellationToken属性
IsCancellationRequested设置为true。
【注意】调用CancellationTokenSource对象的Cancel方法,并不会执行取消操作,而是会将该对象的CancellationToken属性IsCancellationRequested设置为true。
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(); //挂起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 = ;
for (var i = ; i < num; i++)
{
if (token.IsCancellationRequested) //监控CancellationToken
{
return;
} Console.WriteLine($"{i + 1}/{num} 完成");
Thread.Sleep();
}
}
}
运行结果如下:

六:异步方法异常处理
class Program
{
static void Main(string[] args)
{
#region async & await入门二之异步方法异常处理
var task = ExceptionAsync();
task.Wait(); Console.WriteLine($"{nameof(task.Status)}: {task.Status}"); //任务状态
Console.WriteLine($"{nameof(task.IsCompleted)}: {task.IsCompleted}"); //任务完成状态标识
Console.WriteLine($"{nameof(task.IsFaulted)}: {task.IsFaulted}"); //任务是否有未处理的异常标识 Console.Read();
#endregion
} /// <summary>
/// 异常操作
/// </summary>
/// <returns></returns>
private static async Task ExceptionAsync()
{
try
{
await Task.Run(() => { throw new Exception(); });
}
catch (Exception)
{
Console.WriteLine($"{nameof(ExceptionAsync)} 出现异常。");
}
}
}

后记:一、四、五、六也算是C#线程学习笔记七:Task详细用法的一些补充,其它的用法与笔记七的用法差不多的,这里就不再赘述了。
参考自:
https://www.cnblogs.com/liqingwen/p/5844095.html
https://www.cnblogs.com/TianFang/archive/2012/09/21/2696769.html
https://www.cnblogs.com/liqingwen/p/5866241.html
C#线程学习笔记九:async & await入门二的更多相关文章
- C#线程学习笔记十:async & await入门三
一.Task.Yield Task.Yield简单来说就是创建时就已经完成的Task,或者说执行时间为0的Task,或者说是空任务,也就是在创建时就将Task的IsCompeted值设置为0. 我们知 ...
- C#线程学习笔记八:async & await入门一
一.涉及内容 async & await是C# 5.0引入的,控制台输出所使用的$符号(拼接字符串)是C# 6.0引入的,其功能类似于string.Format()方法. 二.多线程.异步.同 ...
- Oracle RAC学习笔记:基本概念及入门
Oracle RAC学习笔记:基本概念及入门 2010年04月19日 10:39 来源:书童的博客 作者:书童 编辑:晓熊 [技术开发 技术文章] oracle 10g real applica ...
- Linux内核学习笔记-1.简介和入门
原创文章,转载请注明:Linux内核学习笔记-1.简介和入门 By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- 多线程学习笔记九之ThreadLocal
目录 多线程学习笔记九之ThreadLocal 简介 类结构 源码分析 ThreadLocalMap set(T value) get() remove() 为什么ThreadLocalMap的键是W ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- jQuery学习笔记 - 基础知识扫盲入门篇
jQuery学习笔记 - 基础知识扫盲入门篇 2013-06-16 18:42 by 全新时代, 11 阅读, 0 评论, 收藏, 编辑 1.为什么要使用jQuery? 提供了强大的功能函数解决浏览器 ...
- 【转载】【时序约束学习笔记1】Vivado入门与提高--第12讲 时序分析中的基本概念和术语
时序分析中的基本概念和术语 Basic concept and Terminology of Timing Analysis 原文标题及网址: [时序约束学习笔记1]Vivado入门与提高--第12讲 ...
- 卷积神经网络(CNN)学习笔记1:基础入门
卷积神经网络(CNN)学习笔记1:基础入门 Posted on 2016-03-01 | In Machine Learning | 9 Comments | 14935 Vie ...
随机推荐
- Python3 之 类属性与实例属性
1.类属性与实例属性 类属性就相当与全局变量,实例对象共有的属性,实例对象的属性为实例对象自己私有. 类属性就是类对象(Tool)所拥有的属性,它被所有类对象的实例对象(实例方法)所共有,在内存中只存 ...
- Flask入门学习——蓝图Blueprint
flask蓝图可以实现应用程序的模块化,即通常作用于相同的url前缀,eg:/user/id,/user/profile等类似这样,可以放在一个模块当中,这样会让应用更加清晰便于开发与维护. 这里有个 ...
- Leetcode_01【两数之和】
文章目录: 题目 脚本一及注释 脚本逻辑 脚本二及注释 脚本逻辑 题目: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. ...
- 从壹开始 [ Design Pattern ] 之三 ║ 工厂模式 与 小故事
编者按: 定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使得一个类的实例化延迟到子类. 工厂模式,是迄今为止,使用最多,最广泛的设计模式之一,它的身影几乎出现在每一个框架和个人代码之中 ...
- num += num 与 num = num+ num
a = 100def test(num): num += num print(num) test(a)print(a) 200100 这里 num += num 与 num = num+ num 不能 ...
- 重启testjenkins的步骤
在linux下编译caffe的过程中,发生错误,导致linux系统蹦了,没办法,重启linux系统. 之前安装在docker下的jenkins也停掉了. 先启动jenkins的步骤如下: 1.先启动d ...
- PHP根据经纬度获取在范围坐标的数据
// 计算范围,可以做搜索用户 function GetRange($lat,$lon,$raidus){ //计算纬度 $degree = (24901 * 1609) / 360.0; $dpmL ...
- mongoDB学习笔记(一)之操作符
本文主要讲解mongoDb的一些常用的操作符的用法.随着作者本身的能力的提高,本文也会不断的完善. 官方文档链接为有: https://docs.mongodb.com/manual/referenc ...
- VMware修改默认开机方式
.首先删除已经存在的符号链接 ---------------------------------------------------------------------------------- rm ...
- Anticancer Effect of Deuterium Depleted Water - Redox Disbalance Leads to Oxidative Stress(低氘水的抗癌作用-氧化还原失衡导致了氧化应激)-解读人:范徉
期刊名:Molecular & Cellular Proteomics 发表时间:(2019年12月) IF:4.828 单位:瑞典卡罗林斯卡学院 物种:人 技术:标记定量蛋白质组学,氧化还原 ...