async/await使用深入详解
async和await作为异步模型代码编写的语法糖已经提供了一段时间不过一直没怎么用,由于最近需要在BeetleX webapi中集成对Task方法的支持,所以对async和await有了深入的了解和实践应用.在这总结一下async和await的使用,主要涉及到:自定义Awaitable,在传统异步方法中集成Task,异常处理等.
介绍
在传统异步方法处理都是通过指定回调函数的方式来进行处理,这样对于业务整非常不方便.毕竟业务信息和状态往往涉及到多个异步回调,这样业务实现和调试成本都非常高.为了解决这一问题dotnet推出了async和await语法糖,该语法可以把编写的代码编译成状态机模式,从而让开发员以同步的代码方式实现异步功能的应用.
应用
async和await的使用非常简单,只需要在方法前加上async关键字,然后await所有返回值为Task或ValueTask的方法即可.大概应用如下:
async void AccessTheWebAsync()
{
var client = new HttpClient();
var result = await client.GetStringAsync("https://msdn.microsoft.com");
Console.WriteLine(result);
}
以上是HttpClient的一个简单应用,它和传统的同步调用有什么不同呢?如果用同步GetString那线程回等待网络请求完成后再进行输出,这样会导致线程资源一直浪费在那里.使用await后,当线程执行GetStringAsync后就会释放出来,然后由网络回调线程来触发后面的代码执行.当然还有一种情况就是GetStringAsync同步完成了当线程就会马上执行Console.WriteLine(result);其实不管那一种情况下都不会让线程等待在那里浪费资源.
自定义Awaitable
一般情况下async和await都是结合Task来使用,因此可能有人感觉async和await是因Task而存在的;其实async和await是一个语法糖,通过它和相应的代码规则来让编译器知道怎样做,但这个规则并不是Task;正确的来说Task是这规则的一种实现,然后应用在大量的方法上,所以自然就使用起来就最普遍了.如果感觉Task太繁琐使用起来比较重的情况下是完全可以自己实现这个规则,这一规则实现起来也很简单只需要简单地实现一个接口和定义一些方法即可:
public interface INotifyCompletion
{
void OnCompleted(Action continuation);
}
看上去是不是很简单,不过除了实现这一接口外,还需要定义一些固定名称的方法
public interface IAwaitCompletion : INotifyCompletion
{ bool IsCompleted { get; } void Success(object data); void Error(Exception error); } public interface IAwaitObject : IAwaitCompletion
{ IAwaitObject GetAwaiter(); object GetResult(); }
在基础上再定义一下些行为就可以了,以上IAwaitObject就是实现一个Awaitable所需要的基础方法行为.不过Success和'Error'方法不是必需要.只是通过这些方法可以让外部来触发OnCompleted行为而已. 围绕接口实现Awaitable的方式也可以根据实际情况应用有所不同,只要需要确保基础规则实现即可,以下是针对SocketAsyncEventArgs实现的Awaitable
public class SocketAwaitableEventArgs : SocketAsyncEventArgs, ICriticalNotifyCompletion
{
private static readonly Action _callbackCompleted = () => { }; private readonly PipeScheduler _ioScheduler; private Action _callback; public SocketAwaitableEventArgs(PipeScheduler ioScheduler)
{
_ioScheduler = ioScheduler;
} public SocketAwaitableEventArgs GetAwaiter() => this; public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted); public int GetResult()
{
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); _callback = null; if (SocketError != SocketError.Success)
{
ThrowSocketException(SocketError);
} return BytesTransferred; void ThrowSocketException(SocketError e)
{
throw new SocketException((int)e);
}
} public void OnCompleted(Action continuation)
{
if (ReferenceEquals(_callback, _callbackCompleted) ||
ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted))
{
Task.Run(continuation);
}
} public void UnsafeOnCompleted(Action continuation)
{
OnCompleted(continuation);
} public void Complete()
{
OnCompleted(this);
} protected override void OnCompleted(SocketAsyncEventArgs _)
{
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); if (continuation != null)
{
_ioScheduler.Schedule(state => ((Action)state)(), continuation);
}
}
}
以上是Kestrel内部实现的一个Awaitable,它的好处就是可以自己不停地复用,并不需要每次await都要构建一个Task对象.这样对于大量处理的情况下可以降低对象的开销减轻GC的负担来提高性能.
传统异步下实现async/await
其实自定义Awaitable就是一种传统异步使用async/await功能的一种实现,但对于普通开发人员来说对于状态不好控制的情况那实现这个Awaitable多多少少有些困难,毕竟还需要大量的测试工作来验证.其实dotnet已经提供TaskCompletionSource<T>对象来方便应用开发者在传统异步下简单实现async/await.这个对象使用起来也非常方便
public Task<Response> Execute()
{
TaskCompletionSource<Response> taskCompletionSource = new TaskCompletionSource<Response>();
OnExecute(taskCompletionSource);
return taskCompletionSource.Task;
}
构建一个TaskCompletionSource<T>对象返回对应的Task即可,然后在异步完成的地方调用相关方法即可简单实现传统异步支持async/await
taskCompletionSource.TrySetResult(response)
或
taskCompletionSource.TrySetError(exception)
在这里不得不说一下TaskCompletionSource<T>的设计,非要加个泛型.如果结合反射使用就有点蛋碎了,毕竟这个方法并不提供object设置,除非上层定义TaskCompletionSource<Object>但这样定义就失去了T的意义了....还好这个类可继承的给使用者留了一个后路.以下做了简单的封装让它支持object返回值传入
interface IAnyCompletionSource
{
void Success(object data);
void Error(Exception error);
void WaitResponse(Task<Response> task);
Task GetTask();
} class AnyCompletionSource<T> : TaskCompletionSource<T>, IAnyCompletionSource
{
public void Success(object data)
{
TrySetResult((T)data);
} public void Error(Exception error)
{
TrySetException(error);
} public async void WaitResponse(Task<Response> task)
{
var response = await task;
if (response.Exception != null)
Error(response.Exception);
else
Success(response.Body);
} public Task GetTask()
{
return this.Task;
}
}
异常处理
由于async/await最终编译成状态机代码,所以异常处理会和普通代码不同,一连串的async/await方法里,一般只需要在最顶的断层方法Try即可,一般这个断层的方法是async void,或Task.wait处;和传统方法异常处理不一样,如果再往上一层是无法Try住这些异常的,当现现这情况的时候往往就是未知异常导致程序死掉.以下是一个错误的处理代码:
static void Main(string[] args)
{
try
{
Test();
}
catch (Exception e_)
{
Console.WriteLine(e_);
}
Console.Read();
} static async void Test()
{
Console.WriteLine(await PrintValue());
} static async Task<bool> PrintValue()
{
var value = await GetUrl();
Console.WriteLine(value);
return true;
} static async Task<string> GetUrl()
{
var client = new HttpClient();
return await client.GetStringAsync("https://msdn.microsoft.comasd");
}
正确有效的Try地方是在Test方法里
static async void Test()
{
try
{
Console.WriteLine(await PrintValue());
}
catch (Exception e_)
{
Console.WriteLine(e_);
}
} static async Task<bool> PrintValue()
{
var value = await GetUrl();
Console.WriteLine(value);
return true;
} static async Task<string> GetUrl()
{
var client = new HttpClient();
return await client.GetStringAsync("https://msdn.microsoft.comasd");
}
一些注意事项和技巧
- 自定义async/await时候,默认都是由异步完成线程来触发状态机,但这里存在一个风险当这个触发状态机的代码是在锁范围内执行就需要特别小心,很多时候再次回归执行获取锁的时候就导致无法得到引起代码无法执行的问题.
- 在使用的await之前其实是可以先判断一下完成状态,如果是完成就没有必然引用await来处理状态机的工作,这样一定程度降低状态的执行和开销.
- 如果你的方法可以是同步完成,如一些内存操作那最好用ValueTask代替Task
- 其实反射里使用async/await也是非常方便的,只需要判断一下对象是否Awaitable,如果是就执行await处理状态机.
async/await使用深入详解的更多相关文章
- async/await 执行顺序详解
随着async/await正式纳入ES7标准,越来越多的人开始研究据说是异步编程终级解决方案的 async/await.但是很多人对这个方法中内部怎么执行的还不是很了解,本文是我看了一遍技术博客理解 ...
- C# 中的Async 和 Await 的用法详解
众所周知C#提供Async和Await关键字来实现异步编程.在本文中,我们将共同探讨并介绍什么是Async 和 Await,以及如何在C#中使用Async 和 Await. 同样本文的内容也大多是翻译 ...
- 关于async和await的一些误区实例详解
转载自 http://www.jb51.net/article/53399.htm 这篇文章主要介绍了关于async和await的一些误区实例详解,有助于更加深入的理解C#程序设计,需要的朋友可以参考 ...
- 详解promise、async和await的执行顺序
1.题目和答案 一道题题目:下面这段promise.async和await代码,请问控制台打印的顺序? async function async1(){ console.log('async1 sta ...
- 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程
反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) 背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...
- Promise和async await详解
本文转载自Promise和async await详解 Promise 状态 pending: 初始状态, 非 fulfilled 或 rejected. fulfilled: 成功的操作. rejec ...
- JavaScript中的async/await详解
1.前言 async函数,也就是我们常说的async/await,是在ES2017(ES8)引入的新特性,主要目的是为了简化使用基于Promise的API时所需的语法.async和await关键字 ...
- async和await详解
async和await详解 1.非UI线程中执行 Test()函数带有async 和await ,返回值写成Task. 1 using System; 2 using System.Threadin ...
- async await详解
async await本身就是promise + generator的语法糖. 本文主要讲述以下内容 async awiat 实质 async await 主要特性 async await 实质 下面 ...
随机推荐
- Effective Java 第三版——43.方法引用优于lambda表达式
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- OpenCV-Python学习01
import tensorflow as tf 1 # -*- coding: utf-8 -*- 2 """ 3 Created on Tue Dec 31 19:16 ...
- js的赋值问题:值传递还是引用传递?
ECMAScript中有5种简单数据类型(也称为基本数据类型):Undefined.Null.Boolean.Number和String.还有1种复杂数据类型--Object,Object本质上是由一 ...
- Charles 连接手机抓包出现Unknown,一直无法抓包的问题解决
mac电脑安装了charles并且确保已经安装成功,https抓包需要安装的相关的证书已经安装,并且手机浏览器中输入chls.pro/ssl已经将证书下载完成,但是手机与电脑相连通过点击手机应用还是无 ...
- 一文带你超详细了解Cookie
cookie 简介 什么是 cookie cookie,有时我们也用其复数形式 cookies,是服务端保存在浏览器端的数据片段.以 key/value的形式进行保存.每次请求的时候,请求头会自动包含 ...
- 如何提高使用Java反射的效率?
前言 在我们平时的工作或者面试中,都会经常遇到“反射”这个知识点,通过“反射”我们可以动态的获取到对象的信息以及灵活的调用对象方法等,但是在使用的同时又伴随着另一种声音的出现,那就是“反射”很慢,要少 ...
- 学习攻略丨如何进阶为一名Web安全高手?
学习Web安全的小伙伴很多,但是能成为Web安全高手却很少,很多人都是从入门到放弃,是真的太难还是学习方法不对? 对于基础薄弱的人来说,一般都是从XSS.SQL注入等简单的漏洞研究入门的.除了了解各种 ...
- 『随笔』.Net 底层 数组[] 的 基本设计探秘 512 子数组
static void Main(string[] args) { Console.ReadKey(); //初始化数组 不会立即开辟内存字节, 只有实际给数组赋值时 才会开辟内存 // //猜测数组 ...
- ASP.NET Core - 利用Windsor Castle实现通用注册
问题引入 在ASP.NET Core - 依赖注入这篇文章里面,我们知道了如何利用ASP.NET Core原生的容器来实现依赖注入的,那我们为什么要替换掉默认的 IoC容器呢?从ASP.NET Cor ...
- C# -Asp.Net.SignalR.Core之Hub
前言 程序员的进步是需要环境的,良好的团队环境,良好的开发环境,会让人进步的更加快速. 所以,我认为,如果一个程序员,在2019年还在用Visual Studio 2005开发,那么,他,大概率,不会 ...