多线程编程学习笔记——async和await(一)

接上文 多线程编程学习笔记——任务并行库(一)

接上文 多线程编程学习笔记——任务并行库(二)

 接上文 多线程编程学习笔记——任务并行库(三)

  接上文 多线程编程学习笔记——任务并行库(四)

 

        通过前面的文章,已经学习了怎么使用线程,怎么使用线程同步,怎么使用线程池,怎么使用任务并行库。尽管通过上面的学习,对于线程的使用越来越简单。有没有更简单的方法呢。

       C# 5.0之后,微软在c#语言中添加了两个关键字async与await,这是在TPL上面的更高一级的抽象,真正简化了异步编程的编程方式,从而有助于我们编写出真正健壮少bug的异步应用程序。下面我先来看一个最简单的示例。

async Task<string> AsyncHello()
{

await Task.Delay(TimeSpan.FromSeconds(2));
Return “ Hello world”;
}

         使用async标记异步函数,建议返回async Task<T>。

         Await只能使用在有async标志的方法内部。在async标记的方法内部最少要有一个await,当然,如果一个也没有,编译也不会报错,但是会有编译警告。如下图。

     

       上面的代码在执行完await调用的代码之行后该方法会直接返回。如果同步执行,执行线程会阻塞2秒之后返回结果,本示例里在执行完await操作后,立即将工作线程放回线程池中,我们会异步等待。2秒后,我们会从线程池中取得工作线程并继续运行其中剩余的异步方法。这就允许我们在等待的2秒的时间里可以重用线程池中的线程,这对提高应用程序的可伸缩性非常重要。通过使用async与await我们拥有了线性的程序控制流程,但是执行过程却是异步的。

 

一、   使用await获取异步操作结果

        本示例是学习await如何获取异步操作结果。同时会与TPL进行比较。

 1.示例代码如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading; 

namespace ThreadAsyncDemo
{

    class Program
    {
        static void Main(string[] args)
        {

            Task t = AsyncWithTPL();
            t.Wait();
            t = AsyncWithAwait();
            t.Wait();
            Console.Read();
        }

 

        static Task AsyncWithTPL()
        {
            Task<string> task1 = GetInfoAsync("Task 1");

            Task task2 = task1.ContinueWith(task =>
              Console.WriteLine(task1.Result), TaskContinuationOptions.NotOnFaulted);
            Task task3 = task1.ContinueWith(task =>
              Console.WriteLine(task1.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);
            return Task.WhenAny(task2, task1);

        }

        async static Task AsyncWithAwait()
        {
            try
            {
                string result = await GetInfoAsync("Task 4");
                Console.WriteLine(result);
            }

            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);

            }
        } 

        async static Task<string> GetInfoAsync(string name)
        {

            await Task.Delay(TimeSpan.FromSeconds(2));
            //throw  new Exception("抛出异常信息!"); 

            return string.Format(" Task {0} 正在运行在线程 ID={1}上。这个工作线程是否是线程池中的线程:{2}", name, 
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); } } }

 

 2.程序运行的结果如下图。

 

         程序同时运行了两个异步操作。其中一个是标准的TPL代码,另一个使用了async与await两个关键字。AsyncWithTPL启动了一个任务,运行两秒之后返回关于工作线程信息的字符串。然后我们定义了一个后续操作,用于在异步操作完成后打印出操作结果,还有另一个后续操作,用于万一有错误时,打印出异常信息。最终返回了一个代表其中一个后续操作任务的任务,并等等其在主函数中完成。

        在asyncWithAwait方法中,我们对任务使用await并得到了相同 的结果。这和编写普通的同步代码的风格一样,即我们获取了任务的结果,打印了出来,如果任务完成时带有错误则捕获异常。关键不同的是这实际上是一个异步操作。使用await后,c#立即创建了一个任务,其中一个有后续操作任务,包含了await操作符后面的所有剩余代码。这个新任务也处理了异常。然后这个任务返回 到主方法并等待共完成 。

       因此可以看出程序中的两段代码在概念上是相同的,使用await由编译 器隐式地处理了异步代码。

  3. 我们把上面注释的抛出异常的代码,取消注释,然后运行程序。得到如下图的结果。

 

            注:在gui与asp.net之类的环境 中不推荐 使用task.wait和taskResult方法同,因为如果代码写的不好,很容易导致死锁。

 

二、   在Lambda表达式中使用await操作符

         本示例学习如何在lambda表达式中使用await。将学习如何编写一个使用了await的匿名方法,并且获取异步执行该方法的结果。

 1.示例代码如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading; 

namespace ThreadAsyncDemo
{

    class Program
    {

        static void Main(string[] args)
        {

            Task t = AsyncProcess();
            t.Wait();       
            Console.Read();
        } 

        async static Task AsyncProcess()
        {

            Func<String, Task<string>> asyncLambda = async name =>
            {

                await Task.Delay(TimeSpan.FromSeconds(2));
                return string.Format(" Task {0} 正在运行在线程 ID={1}上。这个工作线程是线程池的线程:{2}" ,name, 
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread); };
string result = await asyncLambda("async lambda"); Console.WriteLine(result); } } }

 

2.程序运行结果,如下图。

 

        首先不能在main方法中使用async,我们将异步函数移到了asyncProcess中,然后使用async关键字声明了一个lambda表达式。由于 任何lambda表达式的类型都不能通过lambda自身来推断,所以不得不显示地指定类型为一字符串,并返回一个Task<string>对象 。

        然后,我们定义 了lambda表达式体,这个方法虽然定义返回的是一个Task<string>对象 ,但实际上返回的是字符串,却没有编译错误。这是因为c#编译器自动 产生了一个任务并返回给我们。

       最后一步就是打印出lambda 表达式执行后的结果。

 

posted @ 2017-12-18 15:38  DotNet菜园  阅读(3573)  评论(1编辑  收藏  举报