图片 4

关于C# async/await的一些说明

Posted by

图片 1

下文以个人对async/await的理解为基础进行一些说明。

  1. 调用流阻塞:不同于线程阻塞,调用流阻塞只对函数过程起作用,调用流阻塞表示在一次函数调用中,执行函数代码的过程中发生的无法继续往后执行,需要在函数体中的某个语句停止的情形;
public static async void Excute()
 {
       Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute " + DateTime.Now);
       await AsyncTest();
       Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 结束 Excute " + DateTime.Now);
 }

 public static async Task<int> AsyncTest()
 {
        Task.Run(() =>
            {
                Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now);
                Thread.Sleep(1000);
            });
            return 1;
 }
void M(){ int r; Task<int> t = 获取调用F()时的调用阻塞点的Task<int>对象; t.OnCompleted += () => { r = t.Value; }; t.Wait();}

第一组,使用await等待线程。

那么执行流不会在Task.Run()这里停下返回,而是直接“路过”这里,执行后面的语句,打印出Task
End
,然后和一般的程序一样返回。当然新的线程还是会被创建出来并执行,但是这种情况下的程序就不会去等Task.Run()完成了。在我的计算机上输出的结果如下:

图片 2

  1. 新的线程恢复执行,打印0 1 2 3 4
    5
    ,线程执行结束,Task对象的IsCompleted变成true

这也是官方为什么只提供了await调用服务的例子,因为,在程序内调用,await还是要了解后,再使用,才安全。

  1. 调用流阻塞点:调用流阻塞中,执行流所停下来地方的那条语句;
  2. 调用流阻塞返回:不同于线程阻塞,调用流发生阻塞的时候,调用流会立即返回,在C#中,返回的对象可以是Task或者Task<T>
  3. 调用流阻塞异步完成跳转:当调用流阻塞点处的异步操作完成后,调用流被强制跳转回调用流阻塞点处执行下一个语句的情形;
  4. async传染:指的是根据C#的规定:若某个函数F的函数体中需要使用await关键字的函数必须以async标记,进一步导致需要使用await调用F的那个函数F’也必须以async标记的情况;
  5. Task对象的装箱与拆箱:指Task<T>和T能够相互转换的情况。
  6. 异步调用:指以await作为修饰前缀进行方法调用的调用形式,异步调用时会发生调用流阻塞。
  7. 同步调用:指不以await作为修饰前缀进行方法调用的调用形式,同步调用时不会发生调用流阻塞。

await是一种很便捷的语法,他的确会让代码简洁一些,但他主动优化线程的功能,如果不了解就使用,可能会导致一些奇怪的BUG发生。

而在病源隔断方法中,一般会在其他操作完成之后去等待异步操作完成:

首先看下使用约束。

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace ConsoleApp1{ class Program { static void Main(string[] args) { TestMain(); } static void TestMain() { Console.Out.Write("Startn"); GetValueAsync(); Console.Out.Write; Console.ReadKey(); } static async Task GetValueAsync() { await Task.Run=> { Thread.Sleep; for(int i = 0; i < 5; ++i) { Console.Out.WriteLine(String.Format("From task : {0}", i)); } }); Console.Out.WriteLine("Task End"); } }}

 

这个现象我称之为async传染

如图,这样写await AsyncTest();就起作用了。

我们可以把M进行改写,事实上编译器是为我们做了类似下面这样子的工作:

为什么呢?我觉得大家的await与async的打开方式不正确。

根据C#的规定:若某个函数F的函数体中需要使用await关键字则该函数必须以async标记,此时F成为异步方法,于是,这会导致这样子的情况:需要使用await调用F的那个函数F’也必须以async标记


因此,如果我们要定义一个异步方法,那么至少要保证:

2、await 等待的函数必须标记async。

注意和

我们接着往下看,修改AsyncTest如下。然后,此时再调用await
AsyncTest(),你会神奇的发现,依然没有卵用。。。

async/await用于异步操作。

 

这里说C#会自动完成Task<int>到T的装箱和拆箱事实上是不严谨的,因为编译器为我们隐藏了很多细节,这里只是“看起来”像是有这么个过程,但实质上并非如此。

但是,好像await
AsyncTest();还是没启作用。没错,事实就是,他真的不会起作用。。。

这段代码事实上等价于:

图片 3 

在使用C#编写GUI程序的时候,如果有比较耗时的操作(如图片处理、数据压缩等),我们一般新开一个线程把这些工作交给这个线程处理,而不放到主线程中进行操作,以免阻塞UI刷新,造成程序假死。

所以,还是那句话,await等待的是线程,不是函数。

async int M(){ int r = await F(); return r;}

下面从头来讲解,首先看这么一组对比

如果我们不使用await异步调用方法F的话,那么方法F将会被当成同步方法调用,即发生同步调用,这个时候执行流不会遇到调用流阻塞点,因此会直接往下执行,考虑上面的代码如果写成:

可以明确的看到,第二组,线程重新回到了主线程1中,而第一组,已经被优化到了线程4中。

其中F()是一个异步方法,它返回的是Task<int>对象。

Excute方法正常执行,而AsyncTest内运行的线程,自己执行自己的。

传统的做法是直接使用C#的Thread类(也存在别的方式,参考这篇文章)进行操作。传统的做法在复杂的应用编写中可能会出现回调地狱的问题,因此C#目前主要推荐使用async/await来进行异步操作。

首先,我们定义一个普通函数,他的返回值是一个Task,然后我们得到Task后,运行它,再用await等待这个Task。

  1. 在异步方法的调用中会出现新的线程,无论调用层数有多深
  2. 一个新线程应该有且仅有一个阻塞调用点
  3. 异步方法嵌套调用的时候,
    每个嵌套调用的异步方法内部至少要调用一个异步方法或者await一个返回值为Task的同步方法。

图片 4  

  1. 打印Start
  2. 调用GetValueAsync(),执行流转入GetValueAsync(),注意此处是同步调用;
  3. 执行Task.Run(),生成一个新的线程并执行,同时立即返回一个Task对象;
  4. 由于调用Task.Run()时,是以await作为修饰的,因此是一个异步调用,上下文环境保存第4步中返回的Task对象,在此处发生调用流阻塞,而当前的调用语句便是调用流阻塞点,于是发生调用流阻塞返回,执行流回到AysncCall()的GetValueAsync()处,并执行下一步

C#语法——泛型的多种应用

async/await通过对方法进行修饰把C#中的方法分为同步方法和异步方法两类,异步方法命名约定以Async结尾。但是需要注意的是,在调用异步方法的时候,并非一定是以异步方式来进行调用,只有指定了以await为修饰前缀的方法调用才是异步调用

不理解吗?没关系,接着看下去。

// 病源隔断方法void M(){ var task = F(); DoSomething(); if(task.IsCompleted) { // 类似Thread的join()方法 task.Wait(); }}// 异步方法async Task F() { await DoAsync();}

别着急,我们稍作调整,在线程后面增加.GetAwaiter().GetResult()。这句话是干什么用的呢?是用来获取线程返回值的。

void async M(){ int r = await F();}// 异步方法async Task<int> F() { await DoAsync(); return 0;}

 async Task<int>等于int

StartEndFrom task : 0

相关文章

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注