前言

之前写过有关异步的文章,对这方面一直比较弱,感觉还是不太理解,于是会花点时间去好好学习这一块,我们由浅入深,文中若有叙述不稳妥之处,还请批评指正。

话题

(1)是不是将方法用async关键字标识就是异步方法了呢?

(2)是不是没有await关键字的存在async就没有存在的意义了呢?

(3)用异步方法的条件是什么呢,为什么会有这个条件限制?

(4)只能调用.NET Framework内置的用await标识的Task,能否自定义实现呢?

(5)在lambda表达式中是否可以用async和await关键字来实现异步呢(即异步lambda表达式)?

上述抛出这几个话题,明白本文主要讲述的话题以及需要深入了解的知识。

注意:这里我将参照园友【反骨仔】的文章进行进一步解析。

async关键字

例如异步方法是这样的:

        public static async Task<int> asyncMethod()
{
return await Task.Run(() => Calculate());
} static int Calculate()
{
return + ;
}

那要是如下这样写呢?

        public static async Task<int> asyncMethod()
{
var task = Task.Run(() => Calculate());
return task.Result;
}

那上述这种写法是不是也是异步方法呢?答案是【NO】,既然不是异步方法为什么要用async关键字来进行标识呢?不是很容易被我们所误解呢?好了疑问这么多我们一一来解惑。

当方法用async标识时,编译器主要做了什么呢?

(1)告诉编译器这个方法里面可能会用到await关键字来标识该方法是异步的,如此之后,编译器将会在状态机中编译此方法。接着该方法执行到await关键字时会处于挂起的状态直到该异步动作完成后才恢复继续执行方法后面的动作。

(2)告诉编译器解析出方法的结果到返回类型中,比如说Task或者Task<TResult>,也就是说将返回值存储到Task中,如果返回值为void那么此时应该会将可能出现的异常存储到上下文中。

当方法用async标识时,是不是所有调用者都将是异步的呢?

当将方法用async标识时且返回值为void或者Task或者Task<TReuslt>,此时该方法会在当前线程中一直同步执行。用async标识方法并不会影响方法运行完成是否是同步或者异步,相反,它能够将方法划分成多块,有可能有些在异步中运行,以至于这些方法是异步完成的,而划分异步和同步方法的边界就是使用await关键字。也就是说如果在方法中未用到await关键字时则该方法就是一整块没有所谓的划分,会在同步中运行,在同步中完成。

当方法用async标识时,是否会引起方法的调用会被添加到线程池队列中或者是创建一个新的线程呢?

显然不是这样,当用async标识方法时只是显示告诉编译器在该方法中await关键字可能会被用到,当执行到await关键字开始处于挂起的状态知道异步动作执行完成才恢复(异步操作是在状态机中【有关状态机请看这里:Async和Await异步编程的原理】完成,完成后此时才会创建一个线程),这也就是为什么在方法中方法用async标识如果没有用到await关键字IDE会发出警告的原因。

—————————————————————————————————————————————————————————————————

到了这里我们可以得出结论:无论方法是同步还是异步都可以用async关键字来进行标识,因为用async标识只是显示表明在该方法内可能会用到await关键字使其变为异步方法,而且将该异步方法进行了明确的划分,只有用了await关键字时才是异步操作,其余一并为同步操作。

参数为什么不能使用ref和out关键字

返回类型必须为void或者Task或者Task<TResult>和关键字的标识以及其他就不再叙述,其中有一条是不能使用ref和out关键字,你是背书似的铭记了这一条,还是略加思索了呢?你想过没有为何不可呢?

我们知道用ref和out关键字不过是为了在方法里面改变其值,也就是是当同步完成时我们期望被ref或者out关键字修饰的值会被设置,但是它们可能在异步完成时或者之后才会被设置达不到我们预期,所以在异步方法中不能用ref和out关键字。

lambda表达式是否可以异步呢?

返回类型Task参数可以为lambda表达式或者匿名方法对象,那直接对lambda表达式异步是否可行?下面我们来看看

        public static async Task<T2> CallFuncAsync<T1, T2>(T1 t, Func<T1, T2> func)
{
return func.Invoke(t);
} public static async Task<string> GetStringAsync(int value)
{
return await Task.Run(() => "xpy0928");
} public static async Task MainAsync()
{
string value = await CallFuncAsync<int, string>(, async (s) => await GetStringAsync(s));
}

编译后生成如下错误:

由上知异步lambda表达式是不行的,猜测是异步lambda表达式不能转换为表达式树,同时我们看看上述代码,CallFunAsync此时并未是异步方法,上述我们已经叙述过,此时是同步运行,既然上述错误,并且代码也有不可取之处我们接下来一一进行修改。

string value = await CallFuncAsync<int, string>(, async (s) => await GetStringAsync(s));

修改为:

string value = await CallFuncAsync<int, string>(, s => GetStringAsync(s).Result);

解决了编译错误,但是未解决CallFuncAsync为异步运行,我们将其修改为异步运行。既然await是针对于Task而操作,我们将CallFuncAsync中的返回参数设置为Task即可。

        public static async Task<T2> CallFuncAsync<T1, T2>(T1 t, Func<T1, Task<T2>> func)
{
return await func.Invoke(t);
}

则最终调用时我们直接调用即可。

   string value = await CallFuncAsync(, GetStringAsync);

此时CallFuncAsync才算是异步运行。

补充(2016-10-21 23:11)

对于异步表达式有一点其实表述不太正确,其实我一直还是有点怀疑异步lambda表达式真的不行吗,此刻我居然发现这样是可以的:

            var task = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromMilliseconds());
});

如上不正是异步表达式的影子吗,于是我将上述代码进行了改写,如下:

        public static async Task<Action> CallFuncAsync(Action action)
{
return action;
} public static async Task<Action> GetStringAsync()
{
return () => Console.WriteLine("xpy0928");
} public static async Task MainAsync()
{
await CallFuncAsync(async () => await GetStringAsync());
}

此时编译通过,说明表述异步表达式并非一定不可以,只是对于无参数的lambda表达式才可以,而对于有参数的lambda表达式如fun则不能执行异步lambda表达式。

至此可以基本下结论:

异步lambda表达式只对于无参数的lambda表达式 才可以(当然这也就没有了什么意义),而对于有参数的lambda表达式则产生编译错误则不能执行异步(猜测是无法转换成对应的表达式树)。(不知是否严谨或者不妥,若有错误之处,还望对此理解的更透彻的园友给出批评性意见)。

为了验证这一点,我们来看看无参数的func委托例子,如下:

        static async Task<string> GetTaskAsync()
{
await Task.Delay(TimeSpan.FromMilliseconds());
return "xpy0928";
}
var task = Task.Run(async () => await GetTaskAsync());

此时无参数的func委托则编译通过,应该是验证了上述观点(还是有点怀疑我所下的结论)。

await关键字

await关键字是这样用的

await Task.Run(() => "xpy0928");

此时背后究竟发生了什么呢?我们上述也说过异步动作时在状态机中完成,当执行到这里时,编译器会自动生成代码来检测该动作是否已经完成,如果已经完成则继续同步执行await关键字后面的代码,通过判断其状态机状态若未完成则会挂起一个继续的委托为await关键字的对象直到完成为止,调用这个继续动作的委托重新进入未完成的这样一个方法。

比如说: await someObject; 编译器则会生成如下代码:

private class FooAsyncStateMachine : IAsyncStateMachine
{
// Member fields for preserving “locals” and other necessary state
int $state;
TaskAwaiter $awaiter;

public void MoveNext()
{
// Jump table to get back to the right statement upon resumption
switch (this.$state)
{

case : goto Label2;

}

// Expansion of “await someObject;”
this.$awaiter = someObject.GetAwaiter();
if (!this.$awaiter.IsCompleted)
{
this.$state = ;
this.$awaiter.OnCompleted(MoveNext);
return;
Label2:
}
this.$awaiter.GetResult();

}
}

此时讲到这里就要涉及到await背后具体的实现,在Task或者Task<TResult>类里面有这样一个返回类型为 TaskAwaiter 的 GetAwaiter 属性,而TaskAwaiter中有如下属性:

    public struct TaskAwaiter : ICriticalNotifyCompletion, INotifyCompletion
{ public bool IsCompleted { get; }
public void GetResult();
public void OnCompleted(Action continuation);
public void UnsafeOnCompleted(Action continuation);
}

通过IsComplete来判断是否已经完成。这个有什么作用呢?通过看到背后具体实现,我们可以自己简单实现异步扩展方法,当我们在Task中查看其方法会有这样的提示:

下面我们就来实现这样的效果,给TimeSpan添加异步方法:

    public static class Extend
{
public static TaskAwaiter GetAwaiter(this TimeSpan timeSpan)
{ return Task.Delay(timeSpan).GetAwaiter();
}
}

此时异步方法则是这样的:

总结

本节我们详细讲述了async和await关键字的使用和一些基本原理以及解释其原因,希望通过对本文的学习,对大家能够更好的去理解异步,我也在学习中,Over。

参考资料

Async/Await FAQ

await anything;

浅谈async、await关键字 => 深谈async、await关键字的更多相关文章

  1. 转载 浅谈C/C++中的static和extern关键字

    浅谈C/C++中的static和extern关键字 2011-04-21 16:57 海子 博客园 字号:T | T   static是C++中常用的修饰符,它被用来控制变量的存贮方式和可见性.ext ...

  2. 【译】Async/Await(三)——Aysnc/Await模式

    原文标题:Async/Await 原文链接:https://os.phil-opp.com/async-await/#multitasking 公众号: Rust 碎碎念 翻译 by: Praying ...

  3. .NET(C#):await返回Task的async方法

    众所周知,async方法只可以返回void,Task和Task<T>. 对于返回void的async方法,它并不是awaitable,所以其他方法不能用await方法来调用它,而返回Tas ...

  4. 在深谈TCP/IP三步握手&四步挥手原理及衍生问题—长文解剖IP

    如果对网络工程基础不牢,建议通读<细说OSI七层协议模型及OSI参考模型中的数据封装过程?> 下面就是TCP/IP(Transmission Control Protoco/Interne ...

  5. callback vs async.js vs promise vs async / await

    需求: A.依次读取 A|B|C 三个文件,如果有失败,则立即终止. B.同时读取 A|B|C 三个文件,如果有失败,则立即终止. 一.callback 需求A: let read = functio ...

  6. ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法

    问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public ActionResult Asv2() { //dead lock var t ...

  7. MVC 如何在一个同步方法(非async)方法中等待async方法

    MVC 如何在一个同步方法(非async)方法中等待async方法 问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public Actio ...

  8. [译]await VS return VS return await

    原文地址:await vs return vs return await作者:Jake Archibald 当编写异步函数的时候,await,return,return await三者之间有一些区别, ...

  9. ES6新语法之let关键字;有别于传统关键字var的使用

    ES6新语法于2015年发布:而我这个前端小白在17年才接触到.惭愧惭愧!!不过到目前为止,似乎只有FireFox和Chrome对ES6的支持相对良好.不过既然人家ES6已经出来了,还是要跟上技术的潮 ...

随机推荐

  1. Java:单例模式的七种写法

    第一种(懒汉,线程不安全): 1 public class Singleton { 2 private static Singleton instance; 3 private Singleton ( ...

  2. Bootstrap 简洁、直观、强悍的前端开发框架,让web开发更迅速、简单。

    Bootstrap 简洁.直观.强悍的前端开发框架,让web开发更迅速.简单.

  3. druid sql黑名单 报异常 sql injection violation, part alway true condition not allow

    最近使用druid,发现阿里这个连接池 真的很好用,可以监控到连接池活跃连接数 开辟到多少个连接数 关闭了多少个,对于我在项目中查看错误 问题,很有帮助, 但是最近发现里面 有条sql语句 被拦截了, ...

  4. js中函数的一些理论知识

      函数的一些理论知识 1. 函数:                执行一个明确的动作并提供一个返回值的独立代码块.同时函数也是javascript中的一级公民(就是函数和其它变量一样). 2.函数的 ...

  5. [转]关于SVN的操作批处理示例

    为了一句话:不要动手做机器能够做的事情. 天天工作用svn,更新啥的打开目录啥的动作天天在重复.每次写些命令也蛮无聊的,不说了,看下面: @echo off rem 显示部分 @echo 注 意 事 ...

  6. bzoj1455: 罗马游戏 + bzoj2809: Dispatching(可并堆)

    昨天看了可并堆是什么,写的是左偏树 大概就是一棵树 1.有左偏性质,即当前根到左叶子节点距离比到右叶子节点距离大 2.有堆性质,堆顶关键字比子树关键字小 合并两个堆的时候,关键字大的插入到关键字小的那 ...

  7. 升级为iOS9后,默认请求类型为https,如何使用http进行请求会报错(引用他人的)

    升级为iOS9后,默认请求类型为https,如何使用http进行请求会报错 The resource could not be loaded because the App Transport Sec ...

  8. C#_技巧:计算代码块运行的时间

    System.Diagnostics下类Stopwatch,给程序代码块运行计时, 利用start()和stop()方法来标记代码快. 该命名空间下还有一些其他类,可以对程序进行诊断(diagnosi ...

  9. C#集合类型大盘点

    C#集体类型( Collections in C#) 集合是.NET FCL(Framework Class Library)中很重要的一部分,也是我们开发当中最常用到的功能之一,几乎是无处不在.俗话 ...

  10. Morris.js和flot绘制折线图的比较

    [文章摘要] 最近用开源的AdminLTE做框架感觉效果特别好,其针对图表库Morris.js和flot都提供了不错的支持,也都提供了这两者的例子.不过Morris.js是基于Raphael.js来的 ...