日志

async和await的使用总结 ~ 竟然一直用错了c#中的async和await的使用。。

 来源    2020-08-02    0  

对于c#中的async和await的使用,没想到我一直竟然都有一个错误。。

。。还是总结太少,这里记录下。

这里以做早餐为例

流程如下:

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

当使用同步方式实现时,代码是这样的:

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    class Program
    {
        static void Main(string[] args)
        {
            var sw = new Stopwatch();
            sw.Start();
            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!");

            Console.WriteLine($"totol time:{sw.ElapsedMilliseconds/1000}");
            Console.ReadKey();
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static Egg FryEggs(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            Task.Delay(3000).Wait();
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            Task.Delay(3000).Wait();
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
    class Coffee { }
    class Egg { }
    class Bacon { }
    class Toast { }
    class Juice { }
}

运行效果如下:

或表示为这样

同步准备的早餐大约花费了 30 分钟,因为总耗时是每个任务耗时的总和。这里的total time只是用来表示记录下程序运行的时间。

而我以前写的异步代码是这样的:

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    class Program
    {
        static async void Main(string[] args)
        {
            var sw = new Stopwatch();
            sw.Start();
            Coffee cup = PourCoffee();
            Console.WriteLine("coffee is ready");

            Egg eggs = await FryEggsAsync(2);
            Console.WriteLine("eggs are ready");

            Bacon bacon = await FryBaconAsync(3);
            Console.WriteLine("bacon is ready");

            Toast toast = await ToastBreadAsync(2);
            ApplyButter(toast);
            ApplyJam(toast);
            Console.WriteLine("toast is ready");

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");

            Console.WriteLine($"totol time:{sw.ElapsedMilliseconds/1000}");
            Console.ReadKey();
        }

        static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
        {
            var toast = await ToastBreadAsync(number);
            ApplyButter(toast);
            ApplyJam(toast);

            return toast;
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static async Task<Toast> ToastBreadAsync(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            await Task.Delay(3000);
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static async Task<Bacon> FryBaconAsync(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            await Task.Delay(3000);
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            await Task.Delay(3000);
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static async Task<Egg> FryEggsAsync(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            await Task.Delay(3000);
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            await Task.Delay(3000);
            Console.WriteLine("Put eggs on plate");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }

    }
    class Coffee { }
    class Egg { }
    class Bacon { }
    class Toast { }
    class Juice { }
}

效果如下:

 可以看出,这样编写的异步和最初同步版本的总共的耗时大致相同。

这是因为这段代码还没有利用异步编程的某些关键功能。

即上面的异步代码的使用在这里是不准确的。

可以看出,这段代码里面的打印输出与同步是一样的。

这是因为:在煎鸡蛋或培根时,此代码虽然不会阻塞,但是此代码也不会启动任何其他任务。

就造成了异步煎鸡蛋的操作完成后,才会开始培根制作。

但是,对于这里而言,我不希望每个任务都按顺序依次执行。

最好是首先启动每个组件任务,然后再等待之前任务的完成。

例如:首先启动鸡蛋和培根。

同时启动任务

在很多方案中,你可能都希望立即启动若干独立的任务。然后,在每个任务完成时,你可以继续

进行已经准备的其他工作。

就像这里同时启动煎鸡蛋,培根和烤面包。

我们这里对早餐代码做些更改。

正确的做法

第一步是存储任务以便在这些任务启动时进行操作,而不是等待:

Coffee cup = PourCoffee();
Console.WriteLine("coffee is ready");

Task<Egg> eggsTask = FryEggsAsync(2);
Egg eggs = await eggsTask;
Console.WriteLine("eggs are ready");

Task<Bacon> baconTask = FryBaconAsync(3);
Bacon bacon = await baconTask;
Console.WriteLine("bacon is ready");

Task<Toast> toastTask = ToastBreadAsync(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> eggsTask = FryEggsAsync(2);
Task<Bacon> baconTask = FryBaconAsync(3);
Task<Toast> toastTask = ToastBreadAsync(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 eggsTask;
Console.WriteLine("eggs are ready");
Bacon bacon = await baconTask;
Console.WriteLine("bacon is ready");

Console.WriteLine("Breakfast is ready!");

运行效果如下:

 或者

 可以看出,这里一次启动了所有的异步任务。而你仅在需要结果时,才会等待每项任务

这里异步准备的造成大约花费20分钟,这是因为一些任务可以并发进行。

而对于直接  Egg eggs = await FryEggsAsync(2); 的方式,适用于你只需要等待这一个异步操作结果,不需要进行其他操作的时候。

与任务组合

吐司操作由异步操作(烤面包)和同步操作(添加黄油和果酱)组成。

这里涉及到一个重要概念:

异步操作后跟同步操作的这种组合也是一个异步操作。

也就是说,如果操作的任何部分是异步的,整个操作就是异步的。

代码如下:

static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
{
    var toast = await ToastBreadAsync(number);
    ApplyButter(toast);
    ApplyJam(toast);

    return toast;
}

所有,主要代码块现在变为:

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!");
}

高效的等待任务

可以通过使用Task类的方法改进上述代码末尾一系列await语句。

WhenAll 是其中的一个api , 它将返回一个其参数列表中的所有任务都已完成时猜完成的Task,

代码如下

await Task.WhenAll(eggsTask, 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>。

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
    Task finishedTask = await Task.WhenAny(breakfastTasks);
    if (finishedTask == eggsTask)
    {
        Console.WriteLine("eggs are ready");
    }
    else if (finishedTask == baconTask)
    {
        Console.WriteLine("bacon is ready");
    }
    else if (finishedTask == toastTask)
    {
        Console.WriteLine("toast is ready");
    }
    breakfastTasks.Remove(finishedTask);
}

处理已完成任务的结果之后,可以从传递给 WhenAny 的任务列表中删除此已完成的任务。

 进行这些更改后,代码的最终版本将如下所示:

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

namespace AsyncBreakfast
{
    class Program
    {
        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 breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
            while (breakfastTasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(breakfastTasks);
                if (finishedTask == eggsTask)
                {
                    Console.WriteLine("eggs are ready");
                }
                else if (finishedTask == baconTask)
                {
                    Console.WriteLine("bacon is ready");
                }
                else if (finishedTask == toastTask)
                {
                    Console.WriteLine("toast is ready");
                }
                breakfastTasks.Remove(finishedTask);
            }

            Juice oj = PourOJ();
            Console.WriteLine("oj is ready");
            Console.WriteLine("Breakfast is ready!");
        }

        static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
        {
            var toast = await ToastBreadAsync(number);
            ApplyButter(toast);
            ApplyJam(toast);

            return toast;
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("Pouring orange juice");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("Putting jam on the toast");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("Putting butter on the toast");

        private static async Task<Toast> ToastBreadAsync(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("Putting a slice of bread in the toaster");
            }
            Console.WriteLine("Start toasting...");
            await Task.Delay(3000);
            Console.WriteLine("Remove toast from toaster");

            return new Toast();
        }

        private static async Task<Bacon> FryBaconAsync(int slices)
        {
            Console.WriteLine($"putting {slices} slices of bacon in the pan");
            Console.WriteLine("cooking first side of bacon...");
            await Task.Delay(3000);
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("flipping a slice of bacon");
            }
            Console.WriteLine("cooking the second side of bacon...");
            await Task.Delay(3000);
            Console.WriteLine("Put bacon on plate");

            return new Bacon();
        }

        private static async Task<Egg> FryEggsAsync(int howMany)
        {
            Console.WriteLine("Warming the egg pan...");
            await Task.Delay(3000);
            Console.WriteLine($"cracking {howMany} eggs");
            Console.WriteLine("cooking the eggs ...");
            await Task.Delay(3000);
            Console.WriteLine("Put eggs on plate");
            
            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("Pouring coffee");
            return new Coffee();
        }
    }
}

效果如下:

或者

 这种异步的代码实现最终大约花费15分钟,因为一些任务能同时运行,

并且该代码能够同时监视多个任务,只在需要时才执行操作。

总结:

async 和 await的功能最好能做到:

尽可能启动任务,不要在等待任务完成时造成阻塞。

即可以先把任务存储到task,然后在后面需要用的时候,调用await task()方法。

参考网址:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/

相关文章
python 3.5中的async / await关键字是否受到C#中async / await的启发?
问答python 3.5中的async / await(语法和关键字)与C#中的async / await非常相似. C#示例: async void asyncTask(){ await asyncMe ...
1
c# – 在递归方法中使用async/await的正确方法是什么?
问答在递归方法中使用async / await的正确方法是什么?这是我的方法: public string ProcessStream(string streamPosition) { var strea ...
C#中的Async / await vs Task.Run
问答我刚接触这个异步的东西. 请忍受我缺乏的知识. >据说当一个方法遇到等待时--"它告诉等待它在完成时运行方法的其余部分,然后从异步方法返回." 我没有得到这一部分. 那么这是 ...
1
之前是否发现了在C#中使用async/await?
问答在关于async / await的stackoverflow之前的一个问题之后,在我看来,await比营销建议更强大和更通用.它似乎是构建计算表达式的一般方法,就像在F#中一样.因此,经过一番努力,我 ...
1
c# – 在webapi中使用async await的最佳实践
问答我有.NET核心Web API作为服务层.服务层具有所有EF代码. 如果有basecontroller与此代码 protected Task<IActionResult> NewTask( ...
1
在数组javascript的sort函数中实现async / await
问答我正在尝试在量角器ElementArrayFinder上实现一个排序方法.众所周知,所有量角器方法都会返回承诺. 所以我的排序方法有一个依赖于promises解决方案的条件. 我正在使用async / ...
1
.net – OperationContext.Current在第一次等待后为空,当在WCF服务中使用async/await时
问答我在.NET 4.5中使用async / await模式在WCF中实现一些服务方法. 示例服务: 合同: [ServiceContract(Namespace = "http://async ...
1
c# – 为什么属性中的async / await方法永远不会返回
问答好吧,请耐心等待,这可能需要一些解释,因此我有一个简单的帐户控制器; [RoutePrefix("api/account")] [Authorize] [HmacAuthentic ...
2
javascript – 如何使用回调函数中的async await从异步函数返回值?
问答我是nodejs的新手,它是回调地狱,我读到了节点8中的async / await简介,并有兴趣以这种方式实现它 我有一组特定的方法需要以trello API的方式一个接一个地以同步的方式调用 例如 ...
在Node.js中使用async / await进行适当的错误处理
问答我的Node.js express应用程序中有以下代码: router.route('/user') .post(async function(req, res) { if(req.body.pass ...
1
javascript – rxjs在地图中使用async / await Rx.Observable.range(1,5).map
问答我想使用async / await从rxjs获取一个列表.我该怎么办? function getData(num){ return new Promise((resolve, reject)=> ...
1
c# – 在web api控制器中使用async/await或task(.net core)
问答我有一个.net核心API,它有一个控制器,用于构建要返回的聚合对象. 它创建的对象由来自对服务类的3个方法调用的数据组成.它们彼此独立,可以彼此隔离运行. 目前我正在使用任务来提高该控制器的性能.当 ...
1
如何在Python 3.5中使用async/await?
问答#!/usr/bin/env python3 # -*- coding: utf-8 -*- import time async def foo(): await time.sleep(1) foo( ...
1
c# – MVVM中的async/await是无用的吗?
问答在MVVM中,ViewModels通过INotifyPropertyChanged事件更新视图,async / await的酷炫功能似乎没有多大空间;在调用者的捕获同步上下文上执行继续. 那么,如果是 ...
1
reactjs – 如何在Jest中使用async / await对Promise catch()方法行为进行单元测试?
问答说我有这个简单的React组件: class Greeting extends React.Component { constructor() { fetch("https://api.do ...
c# – 使用WinForms中的async/await访问Task.Run中的UI控件
问答我在WinForms应用程序中具有以下代码,其中包含一个按钮和一个标签: using System; using System.IO; using System.Threading.Tasks; us ...
1
c# – 在.Net 4.5中使用async/await编写多线程方法
问答我有一个小问题. 我承认我在.Net 4.5之前没有多次使用多线程,但是使用新的async / await功能我决定尝试一下.我开始尝试它,一切似乎都很好,但我无法在网络上的任何地方找到解决我的&qu ...
1
c# – 为什么async / await在我的ASP.net 5控制台应用程序中不起作用?
问答我在Windows(.NET 4.5.1)和Linux(Mono 4.0.1)上尝试了这个简单的ASP.net 5控制台应用程序,两次都有相同的结果. 注意:我将其称为ASP.net 5控制台应用程序 ...
1
为什么Javascript async / await代码在parellel中运行async
问答我看到这篇好文章从谷歌介绍async/await. 但是,我无法理解为什么这些代码并行运行 async function parallel() { const wait1 = wait(500); c ...
c# – 是否可以在MVC 4 AuthorizeAttribute中使用async/await?
问答我在MVC的AuthorizeAttribute中看到的唯一覆盖是public override void OnAuthorization(AuthorizationContext filterCon ...
1