前言

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

话题

(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. VIM 解决中文乱码

    $ vim ~/.vimrc 加入以下内容 set fileencodings=utf-8,ucs-bom,gb18030,gbk,gb2312,cp936 set termencoding=utf- ...

  2. SecurityContextHolder.getContext().getAuthentication() return null

    <security:http> <security:intercept-url pattern="/web/**" access="IS_AUTHENT ...

  3. Java_类似java.lang.VerifyError: Expecting a stackmap frame at branch target 22 in method的解决方法

    报异常的方法内使用了Java 7的新特性:自动资源释放,类似于try(){},即在try后面跟一括号,在括号里面对一些资源赋值,try里面的代码块执行完毕之后会自动释放try后面的括号中声明的资源. ...

  4. 强制 history 不记住特定的命令

    使用 HISTCONTROL 强制 history 不记住特定的命令将 HISTCONTROL 设置为 ignorespace,并在不想被记住的命令前面输入一个空格: # export HISTCON ...

  5. hbase

    http://www.yiibai.com/hbase/hbase_installation.html http://www.linuxidc.com/Linux/2015-03/114669.htm ...

  6. hibernate中SessionFactory与Session的作用

    首先,SessionFactory是线程安全的,SessionFactory用到了工厂模式. 其创建和销毁需要耗费很大的资源,所以一个应用中的一个数据库一般只对应一个sessionfactory. S ...

  7. mysql不能插入中文

    mysql不能插入中文 解决办法: 1.打开终端,连接数据库  mysql -u root -p; 2.输入 satus; 查看状态 3.输入 set char set 'gbk'; 4.如果是已有的 ...

  8. *HDU1847 博弈

    Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ...

  9. apache rewrite_mod 经典疑问解答

    1.RewriteRule ^(com\/.*)$ index.php?do=$1 问:上面的规则匹配表达式 "^(.*)$" 匹配的内容是什么 答:匹配内容是URI站点目录:/d ...

  10. Java概念性问题

    一.变量命名的五个要素 由字母.数字.“_”和“$” 组成 首字母不能为数字 大小写敏感 不能使用Java的保留字和关键字 可以使用中文命名,但是不建议 二.java的基本数据类型 整数类型:byte ...