C#基础系列——异步编程初探:async和await

简介:

前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法。确实,没有异步的多线程是单调的、乏味的,async和await是出现在C#5.0之后,它的出现给了异步并行变成带来了很大的方便。异步编程涉及到的东西还是比较多,本篇还是先介绍下async和await的原理及简单实现。

C#基础系列目录:

 

之前的那篇 C#基础系列——多线程的常见用法详解 就讲到了多线程new Thread()的方式对于有返回值类型的委托是没有解决方案的,如果需要返回值,必须要依靠异步的方式。了解异步之前,我们先来看看Thread对象的升级版本Task对象:

1、Task对象的前世今生:Task对象是.Net Framework 4.0之后出现的异步编程的一个重要对象。在一定程度上来说,Task对象可以理解Thread对象的一个升级产品。既然是升级产品,那它肯定有他的优势,比如我们上面Thread对象不能解决的问题:对于有返回值类型的委托。Task对象就能简单的解决。

复制代码
     static void Main(string[] args)
        {
            Console.WriteLine("执行GetReturnResult方法前的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            var strRes = Task.Run<string>(() => { return GetReturnResult(); });//启动Task执行方法
            Console.WriteLine("执行GetReturnResult方法后的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            Console.WriteLine(strRes.Result);//得到方法的返回值
            Console.WriteLine("得到结果后的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));

            Console.ReadLine();
        }

        static string GetReturnResult()
        {
            Thread.Sleep(2000);
            return "我是返回值";
        }
复制代码

先来看结果:

从结果分析可知在执行var strRes = Task.Run<string>(() => { return GetReturnResult(); })这一句后,主线程并没有阻塞去执行GetReturnResult()方法,而是开启了另一个线程去执行GetReturnResult()方法。直到执行strRes.Result这一句的时候主线程才会等待GetReturnResult()方法执行完毕。为什么说是开启了另一个线程,我们通过线程ID可以看得更清楚:

复制代码
     static void Main(string[] args)
        {
            Console.WriteLine("执行GetReturnResult方法前的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            var strRes = Task.Run<string>(() => { return GetReturnResult(); });
            Console.WriteLine("执行GetReturnResult方法后的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            Console.WriteLine("我是主线程,线程ID:" + Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine(strRes.Result);
            Console.WriteLine("得到结果后的时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));

            Console.ReadLine();
        }

        static string GetReturnResult()
        {
            Console.WriteLine("我是GetReturnResult里面的线程,线程ID:" + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(2000);
            return "我是返回值";
        }
复制代码

结果:

由此可以得知,Task.Run<string>(()=>{}).Reslut是阻塞主线程的,因为主线程要得到返回值,必须要等方法执行完成。

Task对象的用法如下:

复制代码
            //用法一
            Task task1 = new Task(new Action(MyAction));
          //用法二
            Task task2 = new Task(delegate
            {
                MyAction();
            });
        //用法三
            Task task3 = new Task(() => MyAction());
            Task task4 = new Task(() =>
            {
                MyAction();
            });

            task1.Start();
            task2.Start();
            task3.Start();
            task4.Start();
复制代码

由上可知,Task对象的构造函数传入的是一个委托,既然能传入Action类型的委托,可想而知Action的16中类型的参数又可以派上用场了。于是乎Task对象参数的传递就不用多说了吧。详见 C#基础系列——委托和设计模式(一)里面Action委托的用法。

 

2、初识 async & await。

复制代码
        static void Main(string[] args)
        {
            Console.WriteLine("我是主线程,线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            TestAsync();
            Console.ReadLine();
        }

        static async Task TestAsync()
        {
            Console.WriteLine("调用GetReturnResult()之前,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            var name = GetReturnResult();
            Console.WriteLine("调用GetReturnResult()之后,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            Console.WriteLine("得到GetReturnResult()方法的结果:{0}。当前时间:{1}", await name, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
        }

        static async Task<string> GetReturnResult()
        {
            Console.WriteLine("执行Task.Run之前, 线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
            return await Task.Run(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine("GetReturnResult()方法里面线程ID: {0}", Thread.CurrentThread.ManagedThreadId);
                return "我是返回值";
            });
        }
复制代码

结果:

我们来看看程序的执行过程:

由上面的结果可以得到如下结论:

(1)在async标识的方法体里面,如果没有await关键字的出现,那么这种方法和调用普通的方法没什么区别。

(2)在async标识的方法体里面,在await关键字出现之前,还是主线程顺序调用的,直到await关键字的出现才会出现线程阻塞。

(3)await关键字可以理解为等待方法执行完毕,除了可以标记有async关键字的方法外,还能标记Task对象,表示等待该线程执行完毕。所以await关键字并不是针对于async的方法,而是针对async方法所返回给我们的Task。

(4)是否async关键字只能标识返回Task对象的方法呢。我们来试试:

异步方法的返回类型必须为void、Task或者Task<T>类型。也就是说async要么是void,要么和Task关联。

 

3、除了await关键字,Task对象还有另外一种方式等待执行结果。  

复制代码
     static async Task TestAsync()
        {
            Console.WriteLine("调用GetReturnResult()之前,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            var name = GetReturnResult();
            Console.WriteLine("调用GetReturnResult()之后,线程ID:{0}。当前时间:{1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
            Console.WriteLine("得到GetReturnResult()方法的结果:{0}。当前时间:{1}", name.GetAwaiter().GetResult(), DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss"));
        }
复制代码

这样可以得到相同的结果。

name.GetAwaiter()这个方法得到的是一个TaskAwaiter对象,这个对象表示等待完成的异步任务的对象,并提供结果的参数。所以除了能完成await关键字的等待之外,它还能做一些其他的操作。我们将TaskAwaiter转到定义

复制代码
public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion
    {
        public bool IsCompleted { get; }
        public TResult GetResult();
        public void OnCompleted(Action continuation);
        public void UnsafeOnCompleted(Action continuation);
   }    
复制代码

IsCompleted:获取一个值,该值指示异步任务是否已完成。

GetResult():得到执行的结果。这个方法和await关键字效果相同。

OnCompleted():传入一个委托,在任务执行完成之后执行。

UnsafeOnCompleted():计划与此 awaiter 相关异步任务的延续操作。

由此可以看出,await关键字实际上就是调用了TaskAwaiter对象的GetResult()方法。





本文转自懒得安分博客园博客,原文链接:http://www.cnblogs.com/landeanfen/p/4734252.html,如需转载请自行联系原作者

目录
相关文章
|
3月前
|
编译器 数据处理 C#
C#中的异步流:使用IAsyncEnumerable<T>和await foreach实现异步数据迭代
【1月更文挑战第10天】本文介绍了C#中异步流的概念,并通过使用IAsyncEnumerable<T>接口和await foreach语句,详细阐述了如何异步地迭代数据流。异步流为处理大量数据或需要流式处理数据的场景提供了一种高效且非阻塞性的方法,使得开发者能够更优雅地处理并发和数据流问题。
|
3月前
|
C# 开发者
深入理解C#中的`Task<T>`:异步编程的核心
【1月更文挑战第3天】本文旨在探讨C#中`Task<T>`的使用和理解,作为异步编程模式的核心组件。`Task<T>`允许开发者在不阻塞主线程的情况下执行异步操作,并返回一个指定类型`T`的结果。通过定义返回`Task<T>`的异步方法、使用`async`和`await`关键字、处理异常以及获取任务结果,开发者可以编写出高效且响应迅速的应用程序。此外,本文还介绍了如何配置任务以及实现任务的连续性和组合,为掌握C#中的异步编程提供了全面的指导。
|
1月前
|
C#
C#学习系列相关之多线程(四)----async和await的用法
C#学习系列相关之多线程(四)----async和await的用法
|
8月前
|
C#
C#异步编程
C#异步编程
164 0
|
10月前
|
C#
c#异步编程
c#异步编程原理,await asnyc的使用方法。异步编程是指在程序执行过程中,不需要等待某个操作完成,就可以继续执行后续的代码。
267 0
|
C#
C#中await和async关键字的简单理解
C#中await和async关键字的简单理解
45 0
|
C#
C#网络应用编程,实验七: 异步编程练习
C#网络应用编程,实验七: 异步编程练习
94 1
C#网络应用编程,实验七: 异步编程练习
|
C#
C#网络应用编程,异步编程基础练习
C#网络应用编程,异步编程基础练习
124 1
C#网络应用编程,异步编程基础练习
C#多线程(18):一篇文章就理解async和await
C#多线程(18):一篇文章就理解async和await
222 0
C#多线程(18):一篇文章就理解async和await
|
数据采集 存储 前端开发
C#8.0宝藏好物Async streams
本文我将回顾分享 foreach/yield return/async await语法糖的本质 如何使用异步流 附加探索: 编写一个更有意义的迭代效果
C#8.0宝藏好物Async streams