await和async更多的理解_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > await和async更多的理解

await和async更多的理解

 2017/10/27 19:03:00  谷歌’s  程序员俱乐部  我要评论(0)
  • 摘要:最近有不少网友提起await和async,呵呵,C#5引进的语法糖。这个语法糖还真不好吃,能绕倒一堆初学的朋友,在网上也有很多网友关于这块知识点的争论,有对有错,今天在这里把这个误区好好讲讲。在await(C#参考)这样写道:“await运算符应用于异步方法中的任务,在方法的执行中插入挂起点,直到所等待的任务完成。任务表示正在进行的工作。”不要小看这两句话,内容里暗指的意思还真不少。1)await运算符针对于异步方法2)await插入挂起点3)await等待任务完成4
  • 标签:理解

最近有不少网友提起await和async,呵呵,C# 5引进的语法糖。

这个语法糖还真不好吃,能绕倒一堆初学的朋友,在网上也有很多网友关于这块知识点的争论,有对有错,今天在这里把这个误区好好讲讲。

在await(C# 参考)这样写道:

“await 运算符应用于异步方法中的任务,在方法的执行中插入挂起点,直到所等待的任务完成。 任务表示正在进行的工作。”


不要小看这两句话,内容里暗指的意思还真不少。

1)await 运算符针对于异步方法

2)await 插入挂起点

3)await 等待任务完成

4)任务表式正在进行的工作

带着上面四点,我们暂时停下,因为提到await不禁要联想到一个好基友aysnc

在await(C# 参考)这样写道:

"await 仅可用于由 async 关键字修改的异步方法中"


到这里,我们对上面的四个关键点,提出疑问。

await 运算符针对于异步方法,那如果我们在异步方法里添加入同步方法会怎么样?

  private static async Task<TResult> XAsync()
  {
           
     X(); //X()同步方法
        
     return await XXAsync(); //XXAsync()异步方法
  }

 

然后在mainTest主线程里调用这个XAsync()方法

static void Main(string[] args)
{
   XAsync();

OtherMethod(); }

 

 

在main方法里,网上有网友博客说道:

 

1)XAsync在主线程里不会被调用,直到 awiat XAsync 才会被成功调用。就像linq to sql表达式一像,首先是var results=array.select().where();语句一样,他们只是组装,
并不会执行,要等到foreach(var result in results){ ...}迭代时或者.toList()再真正的查询。

2)XAsync在主线程里会被调用,并不阻止主线程,主线程将继续执行下面的程序!原因是我们写这个方法时候,vs会给我们警告,有图为据

到底是谁正确呢?

呵呵,其实这里两种说法都不正确。

首先,XAsync()在主线程里,直接调用,肯定执行,VS此时也瞎胡闹,警告我们说,“不等待此方法”,这是有个大大的前提!那就是这个方法体内,必须是异步的!
可能说到此不好理解

在XAsync()方法里,上面有一段同步方法X(),此时它是运行在主线程上的同步方法,会阻止主线程,在X()运行完全后,在运行至 return await XXAsync()时,才把主线程调用权交还给调用的方法

在此过程里,并不会产生新的线程,全部运行在主线程上

呵呵,越说越迷糊了。。。

await和async 讲白了点,他们并不会真正意义上的去产生新的线程,我们都知道,产生线程可以用Task类或Thread类。
那么async 标注的方法到底是什么呢?微软给我们的一句单简的话,"await 仅可用于由 async 关键字修改的异步方法中"

这就说明,async是为了await起到一种“配合”作用。而是说async修饰的方法都是异步的,那也太相当然了。

在同步方法里,执行具有async修饰的方法,按同步方法来执行。也就是说X()是运行在主线程上的方法。

至于X()方法体内至于执行同步还是异步决定权由方法体内的方法是否据有异步功能!
就像我们上面一样,XAsync()方法体内的X()就同步,那么X()执行的依然是同步方法一样,而不是运行另外一线程上。

那么问题来了,那我们都直接X()方法多好,await还有何用,或者await 为何不直接调用下X()方法呢?。。。

于是我们继续看下一句XXAsync()方法,我们既然用了await 语法,为什么他可以用?我可以把他去了await吗?

当然是肯定的,就像这样:

private static async Task<TResult> XAsync()
  {
           
     X(); //X()同步方法0
        
     XXAsync();//"异步方法1"

     return await XXXAsync(); //XXAsync()异步方法2
  }


XXAsync() 此时是如何运行的呢?同步还是异步?问题又返回来了,至于同步还是异步,不是这个方法“名词”决定的,而是这个方法体内是如何执行的

如:


 private static async Task XXAsync()
  {  
     X();  
  }

 

此时像上面调用的方式,XXAsync()就是我们平时的同步方法嘛!

但是改下:

private static async Task XXAsync()
  {  
     Task.Run(() =>{
             X();   
      });
  }


依据用相同的方法调用XXAsync它,这个时候,真正的运行在另外“任务上”了哦,会运行在其它线程!

写到这里,我们并没有和await有关哦。

那么在XXAsync()方法前加await到底有何不同?

这里首先要澄清的是:加不加 await 与 XXAsync()是异步还是同步的并没有关系!

await 真正的目的只有一个 在调用异步方法 XXAsync() 时挂起此方法,它认为这个方法是比较耗时的方法,主线程或调用此方法的线程不要在此方法等待。
并同时作个标记,当前执行的线程运行结束时,将由此处恢复运行,所以在await 标记的异步方法下面的代码或方法将不能运行,必须等待这个方法完成!


如:
private static async Task XAsync()
  {
           
     await  XXAsync();

     OtherMothod(); 

  }

 

在运行至 await XXAsync()时,调用方法XAsync()者将不再运行,直接返回,就像我们程序代码中的return语句。这样做的好处是,调用线程,不将等待此耗时线程。直接让调用线程往下运行,

如果调用线程向上一直存在await 链,就像方法套方法里有return一样,会逐层向上返回,一直返回到主线程。

而每个“子线程”去等待耗时的I/O处理,比如 操作数据库和网络流任何,这里特别强调I/O处理,试想下,我们的程序要查询一个数据库,可能要有点时间,此时查询命令可能已运行在了sql数据库里,

如果数据库在远程另外一台机器上呢?我们的"子线程或者任务“只是等待,而此时的主线程可能已完成。

如何理解主线程已完成呢?Asp.net Mvc 的机制就在这里,我们都知道,IIS里的线程池是有限的,每次的Client端请求,都会到线程池里取一个空闲的线程,如果主线程一直在”占用“线程池,

很快线程池就会被利用完啦。此时我们平时说的”吞吐量“的高低就是与此息息相关!当线程池被请求完后,再次有新的Client端请求,要会等待线程池的释放。

而mvc 就引用了控制器里异步方法的机制,原理就是让耗时的线程,直接返回,交给主线程,从而主线程会第一时间释放线程池的占用,而耗时的子线程完成时,将会在await标记从继续运行,

由此可以看出Mvc的异步将大大提高了应用程序的”吞吐量“。

至于具体的mvc异步编程机制与原理,网上一大把,也可以看看mvc的源代码,这里只简单的说下,本文的主旨await标记给异步带来的作用。



话题转回来:

那么我们何时在调用异步方法用await 作”标记“呢?

看看microsoft的经典例子

// Three things to note in the signature:  
//  - The method has an async modifier.   
//  - The return type is Task or Task<T>. (See "Return Types" section.)  
//    Here, it is Task<int> because the return statement returns an integer.  
//  - The method name ends in "Async."  
async Task<int> AccessTheWebAsync()  
{   
    // You need to add a reference to System.Net.Http to declare client.  
    HttpClient client = new HttpClient();  

    // GetStringAsync returns a Task<string>. That means that when you await the  
    // task you'll get a string (urlContents).  
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  1)

    // You can do work here that doesn't rely on the string from GetStringAsync.  
    DoIndependentWork();  2)

    // The await operator suspends AccessTheWebAsync.  
    //  - AccessTheWebAsync can't continue until getStringTask is complete.  
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync.  
    //  - Control resumes here when getStringTask is complete.   
    //  - The await operator then retrieves the string result from getStringTask.  
    string urlContents = await getStringTask;  3)

    // The return statement specifies an integer result.  
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value.  
    return urlContents.Length;  
}  

对上面的例子做了1)、2)、3)红色标记,

在1)处定义了一个网络流操作,认为此方法可能会耗时,如果在此处,添加一个await client.GetStringAsync("http://msdn.microsoft.com")

对程序来说,这是一个异步操作,开辟了一个新线程,同时程序在此处返回给被调线程或UI,等网络流返回结束时继续在运换醒被调线程或主线程,并由于继续往下运行其它方法。
对于像这样的网站,一级一级的向上await 不会造成任何的吞吐量或响应速度的降低,可是新的问题来了,接下来2)处
DoIndependentWork()方法必须等到1)完成才能继续运行,“跳出来站在更高点看下”,这不相当于”同步“了吗?按顺序一步一步来的,子线程关没有给我们太多的优势。

是的,确实如此。

我们知道,让“子线程或任务”干事情,主线程继续他的活儿才对的,所以在1)处,不应该用await,让”线程或任务再跑一会“。
由于我们没有加await,于是主线程或调用线程与调用网络流的子线程”一块运行“了。

当程序运行至3)时,1)标记处的任务可能已经完成或者快要完成,此时用了await目的只有一个,下面的一句话 urlContents.Length 要用到异步结果,必须待等结束,并同时向调用线程或主线程返回标记,

以使调用者最快的响应,而不是等待以至于阻塞。

回过头来看下:我们即要多线程或多任务执行我们的程序,让耗时的任务得到执行,同时又要给调用者快速响应,不管他有没有完成任务! 这才是真正的目的。

再想想我们前面说的,DoIndependentWork()调用,加不加await,方法肯定是执行的,同时与该方法异步还是同步也没有关系,只是要不要做”标记“而已

至于加不加标记,就是上面我们解释的理由,忘了,回过头来看看吧

再来看看下面的问题:

如果一个方法里存在多个await,会如何执行呢?

我们可以按照上面的猜想下,await某些时候功能很像return,多个await,相必,第一个awiat会返回,并作标记,后面的任何代码都要等待 如:


private static async TaskXAsync()
  {
           
    await  XXAsync();

    await  XXXAsync(); 
  }

 

事实情况确实如此,XXXAsync()必须等待XXAsync()方法执行结束!此时不会影响调用者的响应速度,但会影响我们代码的执行效率,这点和两个共步方法稍有区别 

private static async TaskXAsync()
  {
           
    XX();

    XXX(); 
  }
像上面的例子XX()和XXX()两同步方法,不仅按顺序执行,而且调用者也无法拿回调用权,也就是无法及时响应,必须待两个方法都结束为止。

”偷偷的想下“,我想在XX(),XXX()方法前加一个await 不就行了吗?

回过头来想想,上面说过:"await 仅可用于由 async 关键字修改的异步方法中"

实际上我们在VS加上那么await会报错,编译不过!   希望破灭。。。此时已经看出被async修饰的目的。因为XX()和XXX()并没被修饰。

那好了,我们就强制在同步方法上用async !

XX()
{
    code here...
}

 

实际上当我们强制在XX()方法上加上Async时VS已经提示如下:

 

很显然,同步方法,想提高调用者的响应速度是不可能仅仅靠async 就能完成的!根本原因就是调用者与执行方法在同一个线程上。

 

 

 再回过头来继续我们上面的例子

 

private static async TaskXAsync()
  {
           
    await  XXAsync();

    await  XXXAsync(); 
  }

上面已清楚,这两个仅仅按顺序执行,并不能并行执行,势必影响执行效率,那么如何才能让他们并行执行呢?
microsoft有专门的方法 Task.WhenAll(Tasks) 我们可以看看microsoft的例子 如:

await SumPageSizesAsync();
 private async Task SumPageSizesAsync()
        {
            // Make a list of web addresses.
            List<string> urlList = SetUpURLList();


            // Declare an HttpClient object and increase the buffer size. The
            // default buffer size is 65,536.
            HttpClient client = new HttpClient() { MaxResponseContentBufferSize = 1000000 };

            // Create a query.
            IEnumerable<Task<int>> downloadTasksQuery = 
                from url in urlList select ProcessURL(url, client);

            // Use ToArray to execute the query and start the download tasks.
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();

            // You can do other work here before awaiting.

            // Await the completion of all the running tasks.
            int[] lengths = await Task.WhenAll(downloadTasks);

            //// The previous line is equivalent to the following two statements.
            //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks);
            //int[] lengths = await whenAllTask;

            int total = lengths.Sum();

            //var total = 0;
            //foreach (var url in urlList)
            //{
            //    // GetByteArrayAsync returns a Task<T>. At completion, the task
            //    // produces a byte array.
            //    byte[] urlContent = await client.GetByteArrayAsync(url);

            //    // The previous line abbreviates the following two assignment
            //    // statements.
            //    Task<byte[]> getContentTask = client.GetByteArrayAsync(url);
            //    byte[] urlContent = await getContentTask;

            //    DisplayResults(url, urlContent);

            //    // Update the total.
            //    total += urlContent.Length;
            //}

            // Display the total count for all of the web addresses.
            resultsTextBox.Text +=
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
        }

 

// The actions from the foreach loop are moved to this async method.
        async Task<int> ProcessURL(string url, HttpClient client)
        {
            byte[] byteArray = await client.GetByteArrayAsync(url);
            DisplayResults(url, byteArray);
            return byteArray.Length;
        }

 

  private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "http://msdn.microsoft.com",
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }

 

上面的例子很简单了,组合任务Tasks,传给 await Task.WhenAll(Tasks) 这样,多个await 就并行得到了执行。

 

从上面的例子,我们看得出,每个嵌套的方法里,都是层层向上await,这就是await链,不仅要作标记在子线程完成时,在此处”唤醒“同时达到快速响应调用线程,逐层向上返回,结果只有一个,最终让最外层的调用者及时响应,而不用等待,就像MVC原理一样,提高“吞吐量”。

但是中间有一个方法,没向上await,被调用者依然是按照执行的方式决定是同步还是异步。被调者,要是有返回值的,调用者,是没办法获取到返回值的,因为,我们并没办法知道,此方法是否已完成,所以,可能在以后的某段代码中依然要用await 调用。



小结:await与async并不能决定方法是同步还是异步,而真正执行异步的还是靠Task、异步委托或其它方式,await的主要作用是,
挂机耗时异步方法,把控制权及时的交给调用者,并在被调用者完成任务时,能够在此唤醒,并继续执行其它方法。
本节的内容,部分例子只起到说明作用,来原于实践的验证,由于时间仓促,并没有提供完整的案例。
同时本节内容主要用简单的语言来加以说明,希望能给读者阐明原理,如果读者希望更清楚的知道await和async,可以查看源代码


如果对于异步的更多了解请参考:

class="postTitle">大话异步与并行(一)

大话异步与并行(二)

大话异步与并行(三)

 

本文部分实例参考 

await(C# 参考) https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/await

 

使用 Async 和 Await 的异步编程 (C#)  https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/index

 

Task.WhenAll 方法 (IEnumerable<Task>)  https://msdn.microsoft.com/zh-cn/library/windows/apps/hh160384(v=vs.110)

 

 

 

作者:谷歌's(谷歌's博客园)
出处:http://www.cnblogs.com/laogu2/ 欢迎转载,但任何转载必须保留完整文章,在显要地方显示署名以及原文链接。如您有任何疑问或者授权方面的协商,请给我留言。

 

发表评论
用户名: 匿名