任务异步编程模型 - Task asynchronous programming model

26 篇文章 0 订阅

任务异步编程模型 - Task asynchronous programming model

首先,什么是异步?

通过使用异步编程,您可以避免性能瓶颈并增强应用程序的整体响应能力。但是,用于编写异步应用程序的传统技术可能很复杂,这使得它们难以编写、调试和维护。

异步对于可能阻塞的活动至关重要,如果此类活动在同步进程中被阻塞,则整个应用程序必须等待。

当您使用异步方法时,应用程序会继续响应 UI。例如,您可以调整窗口大小或最小化窗口,或者如果您不想等待它完成,您可以关闭应用程序。

为什么要需要任务异步编程模型 ?

任务异步编程模型优于异步代码的抽象。就像往常一样,您将代码编写为一系列语句。您可以阅读该代码,就好像每个语句在下一个开始之前完成一样。

下面我们逐渐展开说明为什么需要异步编程模型和如何使用她。

我们先来看下面的代码示例


using System.Threading.Tasks;
using UnityEngine;

public class Testc : MonoBehaviour
{
    float btime;
    string passtime
    {
        get { return " - 过去了" + (Time.realtimeSinceStartup - btime).ToString("F1"); }
    }
    void Start()
    {
        btime = Time.realtimeSinceStartup;
        int do1 = PourCoffee();
        Debug.Log("咖啡好了." + passtime);

        int eggs = FryEggs(2);
        Debug.Log("鸡蛋好了" + passtime);

        int bacon = FryBacon(3);
        Debug.Log("培根好了" + passtime);

        int toast = ToastBread(2);
        ApplyButter(3);
        ApplyJam(4);
        Debug.Log("面包已经准备好了" + passtime);

        int oj = PourOJ();
        Debug.Log("橙汁已经准备好了" + passtime);
        Debug.Log("早餐好了!" + passtime);
    }

    //倒入一杯橙汁。
    private int PourOJ()
    {
        Debug.Log("倒入一杯橙汁。" + passtime);
        Task.Delay(1000).Wait();
        return 1;
    }

    private void ApplyJam(int toast)
    {
        Debug.Log("在吐司上涂果酱" + passtime);
        Task.Delay(1000).Wait();
    }
    private void ApplyButter(int toast)
    {
        Debug.Log("在吐司上涂黄油" + passtime);
        Task.Delay(1000).Wait();
    }

    private int ToastBread(int slices)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("把一片面包放进烤面包机里" + passtime);
        }
        Debug.Log("开始烤~~~" + passtime);
        Task.Delay(2000).Wait();
        Debug.Log("从烤面包机中取出烤面包片" + passtime);

        return 1;
    }

    private int FryBacon(int slices)
    {
        Debug.Log($"把 {slices} 片培根放锅里" + passtime);
        Debug.Log("培根的第一面..." + passtime);
        Task.Delay(1000).Wait();
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("翻动一片培根" + passtime);
        }
        Debug.Log("培根的另一面..." + passtime);
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("翻动一片培根" + passtime);
        }
        Task.Delay(1000).Wait();
        Debug.Log("把培根放在盘子里" + passtime);

        return 1;
    }

    private int FryEggs(int howMany)
    {
        Debug.Log("加热煎蛋锅。" + passtime);
        Task.Delay(3000).Wait();

        Debug.Log($"烹饪{howMany}个鸡蛋 ..." + passtime);
        Task.Delay(1000).Wait();
        Debug.Log("鸡蛋煎好了放在盘子里" + passtime);

        return 1;
    }

    private int PourCoffee()
    {
        Debug.Log("倒咖啡" + passtime);
        Task.Delay(1000).Wait();
        return 1;
    }
}

这段代码是类似于以下列表的说明来解释如何制作早餐:

  1. 倒一杯咖啡。
  2. 热锅,然后煎两个鸡蛋。
  3. 煎三片培根。
  4. 烤两片面包。
  5. 在吐司中加入黄油和果酱。
  6. 倒入一杯橙汁。

我们先来看下运行结果

在这里插入图片描述
程序在运行后卡顿了大概12秒,因为总和是每个单独任务的时间之和。
在这里插入图片描述

这是一个人(单线程)处理所有这些事情。在你热锅的时候,你是全神贯注的等待锅,不会去做任何事情,也不会去想别的事情。如果您有烹饪经验,您可以异步执行这些指令。你可以热锅的时候去烤面包等等。做早餐是个异步工作的好例子,但是不是并行的。对于并行算法,您需要多个厨师(或线程)。一个做鸡蛋,一个做培根,等等。每个人都将专注于那一项任务。每个厨师(或线程)都会被同步阻塞,等待培根准备好翻转,或者吐司爆开。

计算机不像人类那样解释这些指令。计算机将在每个语句上阻塞,直到工作完成,然后再转到下一个语句。这造成了令人不满意的早餐。在较早的任务完成之前,不会启动较晚的任务。制作早餐需要更长的时间,有些食物在上菜前会变冷。

如果想让计算机异步执行上述指令,就必须编写异步代码。

成功的现代应用程序需要异步代码。在没有语言支持的情况下,编写异步代码需要回调、完成事件或其他掩盖代码原始意图的方法。同步代码的优势在于其分步操作使其易于扫描和理解。传统的异步模型迫使您关注代码的异步性质,而不是代码的基本操作。

不要阻塞,而是等待

前面的代码展示了一个不好的做法:构造同步代码来执行异步操作。正如所写,此代码阻止执行它的线程执行任何其他工作。当任何任务正在进行时,它不会被中断。就好像你把面包放进去后盯着烤面包机。你会忽略任何人和你说话,直到吐司爆裂。

让我们从更新此代码开始,以便在任务运行时线程不会阻塞。使用await关键字提供了一种非阻塞方式来启动任务,然后在该任务完成时继续执行。


using System.Threading.Tasks;
using UnityEngine;

public class Testcwait : MonoBehaviour
{
    float btime;
    string passtime
    {
        get { return " - 过去了" + (Time.realtimeSinceStartup - btime).ToString("F1"); }
    }
    async void Start()
    {
        btime = Time.realtimeSinceStartup;
        int do1 = await PourCoffee();
        Debug.Log("咖啡好了." + passtime);

        int eggs  = await FryEggs(2);
        Debug.Log("鸡蛋好了" + passtime);

        int bacon = await FryBacon(3);
        Debug.Log("培根好了" + passtime);

        int toast = await ToastBread(2);
        await ApplyButter(3);
        await ApplyJam(4);
        Debug.Log("面包已经准备好了" + passtime);

        int oj = await PourOJ();
        Debug.Log("橙汁已经准备好了" + passtime);
        Debug.Log("早餐好了!" + passtime);
    }

    //倒入一杯橙汁。
    private async Task<int> PourOJ()
    {
        Debug.Log("倒入一杯橙汁。" + passtime);
        await Task.Delay(1000);
        return 1;
    }

    private async Task ApplyJam(int toast)
    {
        Debug.Log("在吐司上涂果酱" + passtime);
        await Task.Delay(1000);
    }
    private async Task ApplyButter(int toast)
    {
        Debug.Log("在吐司上涂黄油" + passtime);
        await Task.Delay(1000);
    }

    private async Task<int> ToastBread(int slices)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("把一片面包放进烤面包机里" + passtime);
        }
        Debug.Log("开始烤~~~" + passtime);
        await Task.Delay(2000);
        Debug.Log("从烤面包机中取出烤面包片" + passtime);

        return 1;
    }

    private async Task<int> FryBacon(int slices)
    {
        Debug.Log($"把 {slices} 片培根放锅里" + passtime);
        Debug.Log("培根的第一面..." + passtime);
        await Task.Delay(1000);
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("翻动一片培根" + passtime);
        }
        Debug.Log("培根的另一面..." + passtime);
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("翻动一片培根" + passtime);
        }
        await Task.Delay(1000);
        Debug.Log("把培根放在盘子里" + passtime);

        return 1;
    }

    private async Task<int> FryEggs(int howMany)
    {
        Debug.Log("加热煎蛋锅。" + passtime);
        await Task.Delay(3000);

        Debug.Log($"烹饪{howMany}个鸡蛋 ..." + passtime);
        await Task.Delay(1000);
        Debug.Log("鸡蛋煎好了放在盘子里" + passtime);

        return 1;
    }

    private async Task<int> PourCoffee()
    {
        Debug.Log("倒咖啡" + passtime);
        await Task.Delay(1000);
        return 1;
    }
}

在这里插入图片描述
在这里插入图片描述

对比两次Log发现,红框里的时间,第一次只有一个时间因为是阻塞的,这次运行后时间没有阻塞,另外总运行时间与初始同步版本大致相同。该代码尚未利用异步编程的一些关键特性。

这份代码在你热锅的时候也不会去烤面包或者别的事情,你仍然在等待锅热,但是至少你会回应任何其他事情了。如果是在多个订单的餐厅中,你可以开始做饭同时开始做另一份了。

现在,在等待任何尚未完成的已启动任务时,处理早餐的线程不会被阻塞。GUI 应用程序仍然可以等待用户响应。

启动并发任务 - tasks concurrently

在许多情况下,您希望立即启动几个独立的任务。然后,当每个任务完成时,您可以继续其他准备好的工作。在早餐类比中,这就是您如何更快地完成早餐。您还可以几乎在同一时间完成所有工作。你会得到一份热腾腾的早餐。

并发任务使您能够编写更类似于您实际创建早餐的方式的代码。你会同时开始煮鸡蛋、培根和吐司。由于每个都需要采取行动,因此您会将注意力转移到该任务上,处理下一个行动,然后等待其他需要您注意的事情。

让我们再次修改上面的代码

async void Start()
    {
        btime = Time.realtimeSinceStartup;
        int do1 = await PourCoffee();
        Debug.Log("咖啡好了." + passtime);

        //这里我们不wait鸡蛋了
        Task<int> tegg  = FryEggs(2);

        //这里我们不wait培根了
        Task<int> bacon = FryBacon(3);
        

        int toast = await ToastBread(2);
        await ApplyButter(3);
        await ApplyJam(4);
        Debug.Log("面包已经准备好了" + passtime);

        int oj = await PourOJ();
        Debug.Log("橙汁已经准备好了" + passtime);

        int egg = await tegg;
        Debug.Log("鸡蛋好了" + passtime);
        int ba = await bacon;
        Debug.Log("培根好了" + passtime);

        Debug.Log("早餐好了!" + passtime);
    }

我们修改是去掉了热锅煎鸡蛋和培根的加热的await,把await放到了末尾。

我们看下运行结果
在这里插入图片描述
在这里插入图片描述
我们发现总时间用了6秒,就是因为我们没有等待煎鸡蛋和煎培根的时间而是去做了其他事情。

在这里插入图片描述
看上图,相当于我在热锅煎鸡蛋,煎培根的时候,我不去等待了,而是同时也去烤面包了,并且等面包考好,接下来倒完了果汁我们才拿到了烤糊了的鸡蛋和培根(如果等待烤面包的时间够久)。

这就是并行思维和写法。

写道这里相信大家对任务异步编程模型就有一定的理解了吧。

异步异常 - Asynchronous exceptions

异步的函数如果出现了错误,那么如何来捕获呢,假设我们烤面包时候着火了

private async Task<int> ToastBread(int slices)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("把一片面包放进烤面包机里" + passtime);
        }
        Debug.Log("开始烤~~~" + passtime);
        await Task.Delay(2000);

        throw new InvalidOperationException("面包机着火了");

        Debug.Log("从烤面包机中取出烤面包片" + passtime);

        return 1;
    }

我们运行后会出现警告,制作早餐这个事情会被终止。这里要注意的是,这是故意的,因为一旦烤面包机着火,操作将无法正常进行。

在这里插入图片描述

另外如果要捕获异常,那么可以通过Task.Exception

		//烤面包
        Task<int> toast = ToastBread(2);
        try
        {
            int itoast = await toast;
        }
        catch
        {
            Debug.LogError("出现错误了:" + toast.Exception.ToString());
        }
        await ApplyButter(3);
        await ApplyJam(4);
        Debug.Log("面包已经准备好了" + passtime);

在这里插入图片描述

高效等待任务 - Await tasks efficiently

  1. WhenAll等待
        //这里我们不wait鸡蛋了
        Task<int> tegg  = FryEggs(2);

        //这里我们不wait培根了
        Task<int> bacon = FryBacon(3);

        //烤面包
        Task<int> toast = ToastBread(2);

        await Task.WhenAll(tegg, bacon, toast);
        Debug.Log("鸡蛋好了" + passtime);
        Debug.Log("培根好了" + passtime);
        Debug.Log("烤面包好了" + passtime);

它返回一个Task,当其参数列表中的所有任务都完成时,它就会完成

  1. WhenAny等待
		//这里我们不wait鸡蛋了
        Task<int> tegg  = FryEggs(2);

        //这里我们不wait培根了
        Task<int> bacon = FryBacon(3);

        //烤面包
        Task<int> toast = ToastBread(2);

        var breakfastTasks = new List<Task> { tegg, bacon, toast };
        while (breakfastTasks.Count > 0)
        {
            Task finishedTask = await Task.WhenAny(breakfastTasks);
            if (finishedTask == tegg)
            {
                Debug.Log("鸡蛋好了" + passtime);
            }
            else if (finishedTask == bacon)
            {
                Debug.Log("培根好了" + passtime);
            }
            else if (finishedTask == toast)
            {
                Debug.Log("烤面包好了" + passtime);
            }
            breakfastTasks.Remove(finishedTask);
        }

在这里插入图片描述

只要修改Start函数,增加数组并且遍历。它返回在Task其任何参数完成时完成的 。您可以等待返回的任务,知道它已经完成。以下代码显示了如何使用WhenAny等待第一个任务完成,然后处理其结果。处理完已完成任务的结果后,从传递给 的任务列表中删除该已完成任务WhenAny。

下面是最终代码的完整版


using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;

public class Testcwhen : MonoBehaviour
{
    float btime;
    string passtime
    {
        get { return " - 过去了" + (Time.realtimeSinceStartup - btime).ToString("F1"); }
    }
    async void Start()
    {
        btime = Time.realtimeSinceStartup;
        int do1 = await PourCoffee();
        Debug.Log("咖啡好了." + passtime);

        //这里我们不wait鸡蛋了
        Task<int> tegg  = FryEggs(2);

        //这里我们不wait培根了
        Task<int> bacon = FryBacon(3);

        //烤面包
        Task<int> toast = ToastBread(2);

        var breakfastTasks = new List<Task> { tegg, bacon, toast };
        while (breakfastTasks.Count > 0)
        {
            Task finishedTask = await Task.WhenAny(breakfastTasks);
            if (finishedTask == tegg)
            {
                Debug.Log("鸡蛋好了" + passtime);
            }
            else if (finishedTask == bacon)
            {
                Debug.Log("培根好了" + passtime);
            }
            else if (finishedTask == toast)
            {
                Debug.Log("烤面包好了" + passtime);
            }
            breakfastTasks.Remove(finishedTask);
        }

        //await Task.WhenAll(tegg, bacon, toast);
        //Debug.Log("鸡蛋好了" + passtime);
        //Debug.Log("培根好了" + passtime);
        //Debug.Log("烤面包好了" + passtime);


        await ApplyButter(3);
        await ApplyJam(4);
        Debug.Log("面包已经准备好了" + passtime);

        int oj = await PourOJ();
        Debug.Log("橙汁已经准备好了" + passtime);


        Debug.Log("早餐好了!" + passtime);
    }

    //倒入一杯橙汁。
    private async Task<int> PourOJ()
    {
        Debug.Log("倒入一杯橙汁。" + passtime);
        await Task.Delay(1000);
        return 1;
    }

    private async Task ApplyJam(int toast)
    {
        Debug.Log("在吐司上涂果酱" + passtime);
        await Task.Delay(1000);
    }
    private async Task ApplyButter(int toast)
    {
        Debug.Log("在吐司上涂黄油" + passtime);
        await Task.Delay(1000);
    }

    private async Task<int> ToastBread(int slices)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("把一片面包放进烤面包机里" + passtime);
        }
        Debug.Log("开始烤~~~" + passtime);
        await Task.Delay(2000);

        
        Debug.Log("从烤面包机中取出烤面包片" + passtime);

        return 1;
    }

    private async Task<int> FryBacon(int slices)
    {
        Debug.Log($"把 {slices} 片培根放锅里" + passtime);
        Debug.Log("培根的第一面..." + passtime);
        await Task.Delay(1000);
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("翻动一片培根" + passtime);
        }
        Debug.Log("培根的另一面..." + passtime);
        for (int slice = 0; slice < slices; slice++)
        {
            Debug.Log("翻动一片培根" + passtime);
        }
        await Task.Delay(1000);
        Debug.Log("把培根放在盘子里" + passtime);

        return 1;
    }

    private async Task<int> FryEggs(int howMany)
    {
        Debug.Log("加热煎蛋锅。" + passtime);
        await Task.Delay(3000);

        Debug.Log($"烹饪{howMany}个鸡蛋 ..." + passtime);
        await Task.Delay(1000);
        Debug.Log("鸡蛋煎好了放在盘子里" + passtime);

        return 1;
    }

    private async Task<int> PourCoffee()
    {
        Debug.Log("倒咖啡" + passtime);
        await Task.Delay(1000);
        return 1;
    }
}

在这里插入图片描述

这就是任务异步编程模型的用法了。我们应尽可能开始任务,不要阻止等待任务完成。

参考资料:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值