zoukankan      html  css  js  c++  java
  • c#中的Task异步编程

    https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index翻译

    1. 引入

      Task异步编程模型(TAP)提供了对异步代码的抽象,将代码作为语句序列,可以在每个阶段完成下个阶段开始前读取代码,该过程中,编译器进行了多次转换,因为一些语句可能启动工作并返回正在进行的工作任务。

      Task异步编程的目标就是,启动类似于语句序列的代码,但当任务执行完成时,基于外部资源分配以一个更复杂的顺序执行任务,类似于人们如何为包含异步任务的进程发出指令。

    2. 异步编程

      在本文中,通过一个制作早餐的示例,了解关键字async和await关键字如何使得包含一系列异步指令的操作更容易。

      制造早餐的列表如下:

      (1)倒一杯咖啡;

      (2)将锅加热,然后煎两个鸡蛋;

      (3)炒三片培根;

      (4)吐司两片面包;

      (5)加入黄油和果酱吐司;

      (6)倒一杯橙汁

      烹饪早餐是异步工作的一个很好范例,同一个人可以在一个步骤完成之前去执行另一个步骤。该操作的同步代码简易版如下:

    static void Main(string[] args)
    {
        Coffee cup = PourCoffee();
        Console.WriteLine("coffee is ready");
        Egg eggs = FryEggs(2);
        Console.WriteLine("eggs are ready");
        Bacon bacon = FryBacon(3);
        Console.WriteLine("bacon is ready");
        Toast toast = ToastBread(2);
        ApplyButter(toast);
        ApplyJam(toast);
        Console.WriteLine("toast is ready");
        Juice oj = PourOJ();
        Console.WriteLine("oj is ready");
    
        Console.WriteLine("Breakfast is ready!");
    }

      如果采用上述给出的步骤进行早餐准备,整个效率会非常低下,而事实上,我们可以在锅加热煎鸡蛋的过程中,炒培根,在培根开始之后,就可以将面包放入烤面包机。要想实现动作的异步执行,需要编写异步代码。异步实现的简易代码如下:

    static async void Main(string[] args)
    {
        Coffee cup = PourCoffee();
        Console.WriteLine("coffee is ready");
        Egg eggs =await FryEggs(2);
        Console.WriteLine("eggs are ready");
        Bacon bacon =await FryBacon(3);
        Console.WriteLine("bacon is ready");
        Toast toast =await ToastBread(2);
        ApplyButter(toast);
        ApplyJam(toast);
        Console.WriteLine("toast is ready");
        Juice oj = PourOJ();
        Console.WriteLine("oj is ready");
    
        Console.WriteLine("Breakfast is ready!");
    }

      此时,煎鸡蛋、炒培根和烤面包这三个动作就不需要依次执行,当烹饪鸡蛋或培根时,代码不会阻止,可以同时启动多个组件任务。

    2.1 同时启动任务

      许多情况下,我们希望立即启动多个独立任务,然后,当每个任务完成后,可以继续其他已准备好的工作。在上述早餐实例中,也就是要求更快的完成早餐。.NET Core中,System.Threading.Tasks.Task和相关类可以用来推理正在进行的任务类,该特性使得更容易编写接近实际创建早餐方式的代码。能够同时开始烹饪鸡蛋、培根和吐司。当每个动作需要执行时,我们可以把注意力转移到该任务上,注意下一个动作,然后等待其他需要注意的事情。

      我们可以启动一个任务并保留该工作的Task对象,await在处理结果之前,我们将完成每项任务。对上述创建早餐的代码进行修改,第一步是在操作开始时存储操作,而非等待它们。

    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");
    Task<Egg> eggTask=FryEggs(2);
    Egg eggs=await eggTask;
    Console.WriteLine("eggs are ready");
    Task<Bacon> baconTask=FryBacon(3);
    Bacon bacon=await baconTask;
    Console.WriteLine("bacon is ready");
    Task<Toast> toastTask=ToastBread(2);
    Toast toast=await toastTask;
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");
    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    
    Console.WriteLine("Breakfast is ready!");

      接下来,可以将await在提供早餐前将炒培根和煎鸡蛋语句移至末尾,代码如下:

    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");
    Task<Egg> eggTask = FryEggs(2);
    Task<Bacon> baconTask = FryBacon(3);
    Task<Toast> toastTask = ToastBread(2);
    Toast toast = await toastTask;
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");
    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    
    Egg eggs = await eggTask;
    Console.WriteLine("eggs are ready");
    Task<Bacon> baconTask = FryBacon(3);
    Bacon bacon = await baconTask;
    Console.WriteLine("bacon is ready");

      该代码的效果更好,可以立即启动所有的异步任务,只有在需要结果时才等待每项任务。该代码的实现类似于web应用程序中的代码,能够发出不同微服务的请求,然后将结果组合成单个页面。此时,我们将立即发出所有的请求,然后await所有的任务并组合成web页面。

    2.2 任务组合  

      上述制作早餐的过程中,制作吐司是异步操作(烤面包)和同步操作(添加黄油和果酱)的组合。此时,我们需要知道,异步操作和后续同步操作的组合是异步操作,即如果操作的任意部分是异步的,则整个操作都是异步的。

      下面给出创建工作组合的方法。在供应早餐之前,如果想要在添加黄油和果酱之前等待烘烤面包的任何,则可以使用以下代码表示:

    async Task<Toast> makeToastWithButterAndJamAsync(int number){
          var plainToast=await ToastBreadAsync(number);  
          ApplyButter(plainToast);
          ApplyJsm(plainToast);
          return plainToast;
    }

      上述方法中包含了一个await语句,包含异步操作,该方法代表了烘烤面包的任务,然后添加黄油和果酱,之后返回一个Task<TResult>,表示这三个操作的组合结果。当前代码课修改为:

    static async Task Main(string[] args){
        Coffee cup = PourCoffee();
        Console.WriteLine("coffee is ready");
        var eggsTask = FryEggsAsync(2);
        var baconTask = FryBaconAsync(3);
        var toastTask = makeToastWithButterAndJamAsync(2);  
        var eggs = await eggsTask;
        Console.WriteLine("eggs are ready");
        var bacon = await baconTask;
        Console.WriteLine("bacon is ready");
        var toast = await toastTask;
        Console.WriteLine("toast is ready");
        Juice oj = PourOJ();
        Console.WriteLine("oj is ready");
    
        Console.WriteLine("Breakfast is ready!");
    
        async Task<Toast> makeToastWithButterAndJamAsync(int number)
        {
            var plainToast = await ToastBreadAsync(number);
            ApplyButter(plainToast);
            ApplyJam(plainToast);
            return plainToast;
        }
    
    }

      以上代码的修改说明了异步代码工作的重要性,通过将操作分离为返回任务的新方法来组合任务,可以选择何时等待这项任务,同时启动其他任务

    2.3 有效地等待其他任务

      await可以通过使用Task类的方法来该井前面代码末尾的一系列语句,其中一个API是WhenAll,它返回一个在其参数列表中所有任务完成时完成的Task,如以下代码所示:

    await Task.WhenAll(eggTask,baconTask,toastTask);
    Console.WriteLine("eggs are ready");
    Console.WriteLine("bacon is ready");
    Console.WriteLine("toast is ready");
    Console.WriteLine("Breakfast is ready!");

      另一个选择是使用WhenAny,用它修饰的任务在任何参数完成时都返回一个Task<Task>,我们在知道任务已经完成时,可以等待返回的结果。以下代码显示了如何使用WhenAny等待第一个任务完成然后处理其结果,处理完结果后,从传递给的任务列表中删除该已完成的任务。

    var allTasks=new List<Task>{aggsTask,baconTask,toastTask};
    while(allTask.Any()){
        Task finished=await Task.WhenAny(allTasks);
         if (finished == eggsTask)
        {
            Console.WriteLine("eggs are ready");
            allTasks.Remove(eggsTask);
            var eggs = await eggsTask;
        } else if (finished == baconTask)
        {
            Console.WriteLine("bacon is ready");
            allTasks.Remove(baconTask);
            var bacon = await baconTask;
        } else if (finished == toastTask)
        {
            Console.WriteLine("toast is ready");
            allTasks.Remove(toastTask);
            var toast = await toastTask;
        } else
                allTasks.Remove(finished);
    }
    Console.WriteLine("Breakfast is ready!");    
     

      在所有更改后,最终版本main方法如下:

    static async Task Main(string[] args)
    {
        Coffee cup = PourCoffee();
        Console.WriteLine("coffee is ready");
        var eggsTask = FryEggsAsync(2);
        var baconTask = FryBaconAsync(3);
    var toastTask = makeToastWithButterAndJamAsync(2);
    var allTasks = new List<Task>{eggsTask, baconTask, toastTask};
    while(allTask.Any()){
     Task finished = await Task.WhenAny(allTasks);
    if (finished == eggsTask)
            {
                Console.WriteLine("eggs are ready");
                allTasks.Remove(eggsTask);
                var eggs = await eggsTask;
            } else if (finished == baconTask)
            {
                Console.WriteLine("bacon is ready");
                allTasks.Remove(baconTask);
                var bacon = await baconTask;
            } else if (finished == toastTask)
            {
                Console.WriteLine("toast is ready");
                allTasks.Remove(toastTask);
                var toast = await toastTask;
            } else
                    allTasks.Remove(finished);
    }
    Console.WriteLine("Breakfast is ready!");
    
        async Task<Toast> makeToastWithButterAndJamAsync(int number)
        {
            var plainToast = await ToastBreadAsync(number);
            ApplyButter(plainToast);
            ApplyJam(plainToast);
            return plainToast;
        }
    }
  • 相关阅读:
    pair和map
    lower_bound( )和upper_bound( )
    P1886 滑动窗口 /【模板】单调队列
    数的度(数位dp)
    最小生成树
    刷题-力扣-1052. 爱生气的书店老板
    刷题-力扣-766. 托普利茨矩阵
    刷题-力扣-28. 实现 strStr()
    刷题-力扣-697. 数组的度
    刷题-力扣-1004. 最大连续1的个数 III
  • 原文地址:https://www.cnblogs.com/mo-lu/p/11116690.html
Copyright © 2011-2022 走看看