C#多线程系列之async和await用法详解

目录
  • async和await
    • async
    • await
  • 从以往知识推导
    • 创建异步任务
    • 创建异步任务并返回Task
    • 异步改同步
    • 说说awaitTask
    • 说说 asyncTask<TResult>
    • 同步异步?
    • Task封装异步任务
    • 关于跳到await变异步
    • 为什么出现一层层的await

async和await

async

微软文档:使用 async 修饰符可将方法、lambda 表达式或匿名方法指定为异步。

使用 async 修饰的方法,称为异步方法。

例如:

为了命名规范,使用 async 修饰的方法,需要在方法名称后面加上 Async 。

public async Task<int> TestAsync()
{
    // . . . .
}

Lambda :

        static void Main()
        {
            Thread thread = new Thread(async () =>
            {
                await Task.Delay(0);
            });
        }
        public static async Task<int> TestAsync() => 666;

await

微软文档:await 运算符暂停对其所属的 async 方法的求值,直到其操作数表示的异步操作完成。

异步操作完成后,await 运算符将返回操作的结果(如果有)。

好的,到此为止,async 和 await ,在官方文档中的说明,就这么多。

从以往知识推导

这里,你会跟笔者从以往文章中学习到的知识,去推导,去理解 async 和 await 这两个关键字是如何使用的,又应该怎么合理使用。

这里我们不参考文档和书籍的资料,不要看文档和书籍中的示例,我们要一步步来从任务(Task)中的同步异步开始,慢慢摸索。去分析 async 和 await 两个关键字给我们的异步编程带来了什么样的便利。

创建异步任务

场景:周六日放假了,可以打王者(一种游戏),但是昨天的衣服还没有洗;于是用洗衣机洗衣服,清洗期间,开一局王者(一种游戏)。

我们可以编写一个方法如下:

        static void Main()
        {
            Console.WriteLine("准备洗衣服");

            // 创建一个洗衣服的任务
            Task<int> task = new Task<int>(() =>
            {
                // 模拟洗衣服的时间
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });

            Console.WriteLine("开始洗衣服");

            // 让洗衣机洗衣服
            task.Start();

            Console.WriteLine("我去打王者,让洗衣机洗衣服");
            // 打王者
            Thread.Sleep(TimeSpan.FromSeconds(4));
            Console.WriteLine("打完王者了,衣服洗完了嘛?");

            Console.WriteLine(task.IsCompleted);
            if (task.IsCompleted)
                Console.WriteLine("洗衣服花的时间:" + task.Result);
            else
            {
                Console.WriteLine("在等洗衣机洗完衣服");
                task.Wait();
                Console.WriteLine("洗衣服花的时间:" + task.Result);
            }
            Console.WriteLine("洗完了,捞出衣服,晒衣服,继续打王者去");
        }

创建异步任务并返回Task

上面的示例,虽然说,异步完成了一个任务,但是这样,将代码都放到 Main ,可读性十分差,还要其它什么规范之类的,不允许我们写这样的垃圾代码。于是我们将洗衣服这个任务,封装到一个方法中,然后返回 Task 即可。

在 Program 类中,加入如下一个方法,这个方法用于执行异步任务,并且返回 Task 对象。

        /// <summary>
        /// 执行一个任务
        /// </summary>
        /// <returns></returns>
        public static Task<int> TestAsync()
        {
            Task<int> task = new Task<int>(() =>
            {
                // 模拟洗衣服的时间
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });
            task.Start();
            return task;
        }

Main 方法中,改成

        static void Main()
        {
            Console.WriteLine("准备洗衣服");

            // 创建一个洗衣服的任务
            Task<int> task = TestAsync();
            ... ...

但是,两者差别还是不大。

异步改同步

我们创建了异步方法,去执行一个洗衣服的任务;当打完游戏后,需要检查任务是否完成,然后才能进行下一步操作,这时候就出现了 同步。为了保持同步和获得执行结果,我们使用了 .Wait() 、.Result 。

这里我们尝试将上面的操作转为同步,并且获得执行结果。

    class Program
    {
        static void Main()
        {
            int time = Test();
            // ... ...
        }

        /// <summary>
        /// 执行一个任务
        /// </summary>
        /// <returns></returns>
        public static int Test()
        {
            Task<int> task = new Task<int>(() =>
            {
                // 模拟洗衣服的时间
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });
            task.Start();
            return task.Result;
        }
    }

说说 await Task

Task 和 Task<TResult> ,前者是一个没有返回结果的任务,后者是有返回结果的任务。前面的文章中已经使用过大量的示例,这里我们使用 await ,去完成一些完全相同的功能。

Task

        public static void T1()
        {
            Task task = new Task(() => { });
            task.Wait();
        }
        public static async void T2()
        {
            Task task = new Task(() => {  });
            await task;
        }

说明,await 可以让程序等待任务完成。

Task<TResult>

       public void T3()
       {
           // 获取 Task 任务对象,后面的逻辑过程可以弄成异步
           Task<int> task = TestAsync();

           // 任务是异步在执行,我不理会他
           // 这里可以处理其它事情,处理完毕后,再获取执行结果
           // 这就是异步

           Console.WriteLine(task.Result);
       }
        public async void T4()
        {
            // 使用 await 关键字,代表等待执行完成,同步
            int time = await TestAsync();
            Console.WriteLine(time);
        }

说明:await 可以让程序等待任务执行完成,并且获得执行结果。

看到没有。。。await 关键字,作用是让你等,是同步的,压根不是直接让你的任务变成异步后台执行的。

那为啥提到 async 、await,都是说跟异步有关?不急,后面解释。

说说 async Task<TResult>

async Task<TResult> 修饰一个方法,那么这个方法要返回 await Task<TResult> 的结果。

两种同步方式示例对比:

        public static int Test()
        {
            Task<int> task = new Task<int>(() =>
            {
                // 模拟洗衣服的时间
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });
            task.Start();
            return task.Result;
        }
        public static async Task<int> TestAsync()
        {
            Task<int> task = new Task<int>(() =>
            {
                // 模拟洗衣服的时间
                int time = new Random().Next(2, 6);
                Thread.Sleep(TimeSpan.FromSeconds(time));
                return time;
            });
            task.Start();
            int time = await task;
            return time;
        }

同步异步?

问:async 和 await 不是跟异步方法有关嘛,为啥前面的示例使用了 await ,全部变成同步了?

问:使用 async 和 await 的方法,执行过程到底是同步还是异步?

答:同步异步都行,要同步还是异步,全掌握在你的手上。

  • 你使用 await 去调用一个异步方法,其执行过程就是同步。
  • 你获取异步方法返回的 Task,就是异步。

最近笔者收到一些提问,有些读者,使用 async 和 await 去编写业务,想着是异步,可以提升性能,实际结果还是同步,性能一点没有提升。通过下面的示例,你会马上理解应该怎么用。

首先,在不使用 async 和 await 关键字的情况下,我们来编写两个方法,分别实现同步和异步的功能,两个方法执行的结果是一致的。

        /// <summary>
        /// 同步
        /// </summary>
        /// <returns></returns>
        public static int Test()
        {
            Task<int> task = new Task<int>(() =>
            {
                return 666;
            });
            task.Start();
            return task.Result;
        }

        /// <summary>
        /// 异步
        /// </summary>
        /// <returns></returns>
        public static Task<int> TestAsync()
        {
            Task<int> task = new Task<int>(() =>
            {
                return 666;
            });
            task.Start();
            return task;
        }

能不能将两个方法合并在一起呢?想同步就同步,想异步就异步,这样就不需要写两个方法了!

是可以的!通过 async 和 await 关键字,可以轻松实现!

合并后,代码如下:

        /// <summary>
        /// 可异步可同步
        /// </summary>
        /// <returns></returns>
        public static async Task<int> TestAsync()
        {
            Task<int> task = new Task<int>(() =>
            {
                return 666;
            });
            task.Start();
            return await task;
        }

合并后,我们又应该怎么在调用的时候,实现同步和异步呢?

笔者这里给出两个示例:

        // await 使得任务同步
        public async void T1()
        {
            // 使用 await 关键字,代表等待执行完成,同步
            int time = await TestAsync();
            Console.WriteLine(time);
        }

        // 直接获得返回的 Task,实现异步
        public void T2()
        {
            // 获取 Task 任务对象,后面的逻辑过程可以弄成异步
            Task<int> task = TestAsync();

            // 任务是异步在执行,我不理会他
            // 这里可以处理其它事情,处理完毕后,再获取执行结果
            // 这就是异步

            Console.WriteLine(task.Result);
        }

至此,理解为什么使用了 async 和 await,执行时还是同步了吧?

Task封装异步任务

前面,我们都是使用了 new Task() 来创建任务,而且微软官网大多使用 Task.Run() 来编写 async 和 await 的示例。

因此,我们可以修改前面的异步任务,改成:

        /// <summary>
        /// 可异步可同步
        /// </summary>
        /// <returns></returns>
        public static async Task<int> TestAsync()
        {
            return await Task.Run<int>(() =>
            {
                return 666;
            });
        }

关于跳到 await 变异步

在百度学习异步的时候,往往会有作者说,进入异步方法后,同步执行代码,碰到 await 后就是异步执行。

当然还有多种说法。

我们已经学习了这么多的任务(Task)知识,这一点十分容易解释。

因为使用了 async 和 await 关键字,代码最深处,必定会出现 Task 这个东西,Task 这个东西本来就是异步。碰到 await 出现异步,不是因为 await 的作用,而是因为最底层有个 Task。

为什么出现一层层的 await

这是相对于提供服务者来说。因为我要提供接口给你使用,因此底层出现 async、await 后,我会继续保留方法是异步的(async),然后继续封装,这样就有多层的调用结构,例如上一小节的图。

但是如果来到了调用者这里,就不应该还是使用 async 、await 去编写方法,而是应该按照实际情况同步或异步。

到此这篇关于C#多线程系列之async和await用法详解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#多线程用法详解

    目录 一.基本概念 1.进程 2.线程 二.多线程 2.1 System.Threading.Thread类 2.2线程的常用属性 2.2.1 线程的标识符 2.2.2 线程的优先级别 2.2.3 线程的状态 2.2.4 System.Threading.Thread的方法 2.3 前台线程和后台线程 2.4 线程同步 2.5 跨线程访问 2.6 终止线程 三.同步和异步 四.回调 一.基本概念 1.进程 首先打开任务管理器,查看当前运行的进程: 从任务管理器里面可以看到当前所有正在运行的进程.

  • C# 多线程学习之基础入门

    目录 同步方式 异步多线程方式 异步多线程优化 异步回调 异步信号量 异步多线程返回值 异步多线程返回值回调 线程(英语:thread)是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务.进程是资源分配的基本单位.所有与该进程有关的资源,都被记录在进程控制块PCB中.以表示该进程拥有这些资源或正在使用它们.本文以一些简单的小例子,简述如何将程序由同步方式,一步一步演变成

  • C#多线程开发之任务并行库详解

    目录 前言 任务并行库 一.创建任务 二.使用任务执行基本操作 三.处理任务中的异常 总结 前言 之前学习了线程池,知道了它有很多好处. 使用线程池可以使我们在减少并行度花销时节省操作系统资源.可认为线程池是一个抽象层,其向程序员隐藏了使用线程的细节,使我们可以专心处理程序逻辑,而不是各种线程问题. 但也不是说我们所有的项目中都上线程池,其实它也有很多弊端,比如我们需要自定义使用异步委托的方式才可以将线程中的消息或异常传递出来.这些如果在一个大的软件系统中,会导致软件结构过于混乱,各个线程之间消

  • 深入了解C#多线程安全

    目录 什么是多线程安全? 多线程安全示例 1. 多线程不安全示例1 2. 多线程不安全示例2 加锁lock 加锁原理 为何锁对象要用私有类型? 为什么锁对象要用static类型? 加锁锁定的是什么? 泛型锁对象 递归加锁 前面两篇文章,分别简述了多线程的使用和发展历程,但是使用多线程无法避免的一个问题就是多线程安全.那什么是多线程安全?如何解决多线程安全?本文主要通过一些简单的小例子,简述多线程相关的问题,仅供学习分享使用,如有不足之处,还请指正. 什么是多线程安全? 一段程序,单线程和多线程执

  • 深入学习C#多线程

    目录 一.基本概念 1.进程 2.线程 二.多线程 2.1System.Threading.Thread类 2.2 线程的常用属性 2.2.1线程的标识符 2.2.2线程的优先级别 2.2.3线程的状态 2.2.4System.Threading.Thread的方法 2.3前台线程和后台线程 2.4线程同步 2.5跨线程访问 2.6终止线程 三.同步和异步 四.回调 获取委托异步调用的返回值 一.基本概念 1.进程 首先打开任务管理器,查看当前运行的进程: 从任务管理器里面可以看到当前所有正在运

  • 详解C#异步多线程使用中的常见问题

    目录 异常处理 线程取消 临时变量 线程安全 异常处理 小伙伴有没有想过,多线程的异常怎么处理,同步方法内的异常处理,想必都非常非常熟悉了.那多线程是什么样的呢,接着我讲解多线程的异常处理 首先,我们定义个任务列表,当 11.12 次的时候,抛出一个异常,最外围使用 try catch 包一下 static void Main(string[] args) { Console.WriteLine($"Main Start,ThreadId:{Thread.CurrentThread.Manage

  • C# 异步多线程入门基础

    目录 进程.线程 1. 进程 2. 线程 分时.分片 同步.异步 异步.多线程 异步多线程效率 多线程无序性 扩展 异步多线程版本 下一篇:C# 异步多线程入门到精通之Thread篇 进程.线程 1. 进程 首先了解,什么是线程? 即一个应用程序运行时,占用资源的综合是一个进程.Windows 任务管理器里面可以看到,里面一个个都是在运行的进程. 2. 线程 线程是执行流的最小单位.线程其实是看不到的,其实也可以,例如 Windows 任务管理器:正在运行 272 个进程,272 个进程运行了

  • c#多线程通信之委托事件

    在研究c# 线程之间通信时,发现传统的方法大概有三种: 全局变量,由于同一进程下的多个进程之间共享数据空间,所以使用全局变量是最简单的方法,但要记住使用volatile进行限制. 线程之间发送消息(这个随后文章中会讨论到). CEvent为MFC中的一个对象,可以通过对CEvent的触发状态进行改变,从而实现线程间的通信和同步,这个主要是实现线程直接同步的一种方法. 本文介绍的一种方法是这三种之外的一种方法,本文中实例是通过创建一个线程类,通过委托事件把值传送到Form所在的类中,同时更新For

  • C#多线程基础知识汇总

    最近自己写了个小爬虫,里面用到了多线程技术,忽然发现对此技术竟然有些陌生了,于是乎开始疯狂的去问度娘,在此记录下来,以便自己和各位小伙伴们学习. 一.什么是线程 一个应用程序就相当于一个进程,进程拥有应用程序的所有资源进程包括线程,进程的资源被线程共享,但不拥有线程.我们可以打开电脑中的任务管理器,运行的.exe都是一个进程,里面的分支是线程. 二.多线程 多线程其实就是进程中一段并行运行的代码 1. 创建并启动线程 static void Main() { //获取线程Id var threa

  • C#多线程开发实战记录之线程基础

    目录 前言 线程基础 1.创建线程 2.暂停线程 3.线程等待 4.线程终止 C#中的lock关键字 总结 前言 最近由于工作的需要,一直在使用C#的多线程进行开发,其中也遇到了很多问题,但也都解决了.后来发觉自己对于线程的知识和运用不是很熟悉,所以将利用几篇文章来系统性的学习汇总下C#中的多线程开发. 线程基础 "进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元" 这句话应该学习计算机的朋友或多或少都听说过,这在操作系统这门课中是很重要的一个概念. 在操作系统中可以同时

  • 详解C#多线程编程之进程与线程

    一. 进程 简单来说,进程是对资源的抽象,是资源的容器,在传统操作系统中,进程是资源分配的基本单位,而且是执行的基本单位,进程支持并发执行,因为每个进程有独立的数据,独立的堆栈空间.一个程序想要并发执行,开多个进程即可. Q1:在单核下,进程之间如何同时执行? 首先要区分两个概念--并发和并行 并发:并发是指在一段微小的时间段中,有多个程序代码段被CPU执行,宏观上表现出来就是多个程序能"同时"执行. 并行:并行是指在一个时间点,有多个程序段代码被CPU执行,它才是真正的同时执行. 所

  • 深入了解c#多线程编程

    一.使用线程的理由 1.可以使用线程将代码同其他代码隔离,提高应用程序的可靠性. 2.可以使用线程来简化编码. 3.可以使用线程来实现并发执行. 二.基本知识 1.进程与线程:进程作为操作系统执行程序的基本单位,拥有应用程序的资源,进程包含线程,进程的资源被线程共享,线程不拥有资源. 2.前台线程和后台线程:通过Thread类新建线程默认为前台线程.当所有前台线程关闭时,所有的后台线程也会被直接终止,不会抛出异常. 3.挂起(Suspend)和唤醒(Resume):由于线程的执行顺序和程序的执行

随机推荐