C#-Async关键字(异步方法)
async关键字(异步方法)async关键字是C#特有的。Java没有这玩意。async关键字用来修... async关键字用来修饰两类方法: lambda表达式或者异步方法。 拥...
async关键字(异步方法)
async关键字是C#特有的。Java没有这玩意。
async在C#世界里是上下文关键字。它只有在修饰一个方法的时候才自动被编译器识别为关键字,在代码的其他位置上可以被用作变量名等其他任何用途。
async关键字用来修饰两类方法: lambda表达式或者异步方法。
拥有async修饰的方法称为async方法,比如:
public async Task<int> ExampleMethodAsync()
{
// (1) To do some code here synchronously...
await .....// (2) To do something asynchronously....
// (3) To do some code here after awiat code...
}
就如上面这个方法ExampleMethodAsync(),微软爷爷特别喜欢在定义异步函数名字后习惯加个Async后缀(这不是必须的,加不加编译器既不会报错,也不会影响异步特性),告诉我们这个方法是个异步方法。我们在自己定义异步方法的时候,也可以照搬这个微软的习惯。
async修饰的方法内部,应当出现一个await关键字,两个关键字一般成对出现。当然,如果我们不小心忘记写await表达式或者语句,这个async方法默认按照同步方式运行,同时,编译器会友好地提示我们是不是漏写了await。此外,async方法内部,可以有多个await语句。
awiat运行的语句,一般都是比较费时的任务(也就是会阻塞主线程的一些操作,比如获取Http应答,写入文档,保存数据库等),要不然就不需要异步了。
以上面的例子为例(假设例子中的await是第一个await),异步方法执行过程(比较粗的看):
- 主线程进入方法ExampleMethodAsync()后,先顺序执行(1); 如果(1)当中有创建Task或Task<TResult>的语句,或者是调用其他async方法(返回值是Task后者Task<TResult>),为了描述方便,我们都称为Task创建语句; 比如直接创建一个Task或Task<TResult>:
var tsk = Task.Run(()=>{
Thread.Sleep(1000);
Console.Writeline("Do another job asynchronously.");
});
或者调用另外一个async方法:
Task<string> getStringTask = client.GetStringAsync("https://docs.microsoft.com/dotnet");
那么,在调用完Task创建语句的时候异步任务就已经开始运行了(这个语句调用本身是在主线程当中,内部的任务则是新的线程中执行),也就是此时异步的线程已经启动了,由于它是异步启动的,所以它并不会阻止主线程继续往下走;
- 接下来,主线程会顺序运行到async方法内部的第一个await,如果第一个await调用的仍然是一个async方法,那么主程序继续进入这个方法执行,一直到碰到一个await task为止,主线程才会跳出ExampleMethodAsync方法; 举个例子:
static void Main(string[] args)
{
// do something...
ExampleMethodAsync();
// do someting else...
}
public static async void ExampleMethodAsync()
{
// (1) 执行一些任务Do2Async()前准备的事情...
await Do2Async(); // (2)
// (3) 运行一些Do2Async()执行完之后的事情...
}
public static Task Do2Async()
{
// 执行一些t任务执行前的事情,比如任务的准备...
Task t = Task.Run(()=>{
// 异步任务中执行费时的事情...
});
// 运行一些与t无关的事情...
await t;
// 在这里执行一些t任务执行完相关的事情...
}
调用方(也就是main所在的主线程)会一直执行到20行才跳出ExampleMethodAsync()方法,而不是在第10行。
- ExampleMethodAsync()方法中剩余的(3)在执行完await(2)部分的内容才执行。
- 假设ExampleMethodAsync()中有第二个,第三个…awiat,因为主程序已经跳出来了,后续的await会在异步线程中按顺序执行下去。
async方法可以是下面三种返回类型:
- Task
- Task<TResult>
- void 这种返回类型一般用在event事件处理器中,或者用在你只需要任务执行,不关心任务执行结果的情况当中。
- 任何其他具有GetAwaiter方法的类型(从C#7.0开始)
注意,我们无法等待(awiat)一个async void 方法。
using System;
using System.Threading.Tasks;
using System.Threading;
namespace test
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} Hello, I am Caller!");
DoAsync();
Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} Hello, I am Caller too!");
Console.Read();
}
public static async void DoAsync()
{
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} In DoAsync(), before SunAsync()");
await SunAsync();
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} After SunAsync(), DoAsync() End.");
}
public static async Task SunAsync()
{
var t = Task.Run(()=>{
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} New Task~");
for(int i=0 ; i<10; i++)
{
Thread.Sleep(1000);
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} I am playing game...");
}
});
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} After Task, before await.");
await t;
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} After await, before SunAsync() exit.");
}
}
}
此时的结果:
ThreadID:1 Hello, I am Caller!
ThreadID:1 In DoAsync(), before SunAsync()
ThreadID:1 After Task, before await.
ThreadID:4 New Task~
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:4 I am playing game...
ThreadID:1 After await, before SunAsync() exit.
ThreadID:1 After SunAsync(), DoAsync() End.
ThreadID:1 Hello, I am Caller too!
仔细阅读这段代码和结果,细心体会,这段代码是ansync void方法嵌入调用ansync Task方法。要注意体会,并不是说一遇到await主程序(ansync 方法的调用方)就立即退出DoAsync()方法,而是执行到33行,碰到了第一个的Task才跳出来。 从这个例子的输出ThreadID号中,可知,33行await之后的内容都是在新的线程(4线程)中运行的。而33行await之前的内容都在主线程(1线程)中运行。
如果将SunAsync()代码改为(await之前增加一个Thread.Sleep(150000)):
public static async Task SunAsync()
{
var t = Task.Run(()=>{
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} New Task~");
for(int i=0 ; i<10; i++)
{
Thread.Sleep(1000);
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} I am playing game...");
}
});
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} After Task, before await.");
Thread.Sleep(15000); //主线程睡15秒
await t;
System.Console.WriteLine($"ThreadID:{Thread.CurrentThread.ManagedThreadId} After await, before SunAsync() exit.");
}
ThreadID:1 Hello, I am Caller!
ThreadID:1 In DoAsync(), before SunAsync()
ThreadID:1 After Task, before await.
ThreadID:4 New Task~
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:4 I am playing game…
ThreadID:1 After await, before SunAsync() exit.
ThreadID:1 After SunAsync(), DoAsync() End.
ThreadID:1 Hello, I am Caller too!
因为,Task.Run()的任务在运行到await之前就结束了,因此,await后的内容仍然在主线程(1线程)中执行。这个例子告诉我们,如果任务在await之前就已经执行完毕,那么await后的内容仍然保留在原线程中执行。
总之,async方法调用方在碰到一个实际的await task的时候才退出async方法体。一般在await之前处理与异步任务无关的事情(这部分代码是由异步方法的调用方所在的线程执行),await之后的代码则是处理异步任务处理完后的事情,因此这部分代码就可以处理与异步任务相关的事情(这部分一般来说是在新建的异步线程中执行的,除非在调用await之前任务就已经很快的执行完了,那么这部分内容也可能仍然在调用方线程中执行)。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)