async关键字(异步方法)

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),异步方法执行过程(比较粗的看):

  1. 主线程进入方法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创建语句的时候异步任务就已经开始运行了(这个语句调用本身是在主线程当中,内部的任务则是新的线程中执行),也就是此时异步的线程已经启动了,由于它是异步启动的,所以它并不会阻止主线程继续往下走;

  1. 接下来,主线程会顺序运行到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行。

  1. ExampleMethodAsync()方法中剩余的(3)在执行完await(2)部分的内容才执行。
  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:1  Hello, I am Caller too!
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:4  After await, before SunAsync() exit.
ThreadID:4  After SunAsync(), DoAsync() End.

仔细阅读这段代码和结果,细心体会,这段代码是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之前任务就已经很快的执行完了,那么这部分内容也可能仍然在调用方线程中执行)。

发布了108 篇原创文章 · 获赞 126 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/qq_16587307/article/details/103857625
今日推荐