对于程序员来说,async/await语法是否真的比手动线程更好,或者有时它不是那么有用

本文关键字:或者 线程 更好 有用 async 程序员 await 真的 是否 语法 | 更新日期: 2023-09-27 18:06:18

我经常在网上看到async'await是编程界的"天才创新"。有时候是,但在某些情况下,我觉得它不能缩短需要编写的代码。

如果我需要3个并行任务(下载),我想对每个下载的结果做一些事情(其中处理(输出SUM到控制台)依赖于每个下载,我需要同时得到所有结果,如#1所示),我可以使用线程而不使用async/await,如这里(一些伪代码,因为GetByteArray不存在):

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
    class Program
    {
        static int length = 0;
        static HttpClient client =
            new HttpClient() { MaxResponseContentBufferSize = 1000000 };
        static void Main(string[] args)
        {
            CreateMultipleTasksAsync();
            Console.ReadKey();
        }
        static void CreateMultipleTasksAsync()
        {
            Thread th1 = new Thread(ProcessURLAsync);
            th1.Start("http://msdn.microsoft.com");
            Thread th2 = new Thread(ProcessURLAsync);
            th2.Start("http://msdn.microsoft.com/en-us/library/hh156528(VS.110).aspx");
            Thread th3 = new Thread(ProcessURLAsync);
            th3.Start("http://msdn.microsoft.com/en-us/library/67w7t67f.aspx");
            //#2 there I need results of all 3 downloads (so it will wait for all 3 downloads being completed)
            Console.WriteLine("'r'n'r'nTotal bytes returned:  {0}'r'n", Program.length);
        }
        static void ProcessURLAsync(object urlObj)
        {
            string url = (string)urlObj;
            var length = client.GetByteArray(url).Length;
     /*       //#1 there I need only a result of one current download (so it will wait only for current download being completed)
            Console.WriteLine("'n{0,-58} {1}", url, length);*/
            Program.length =+ length;
        }
    }
}

或者使用async/await:

using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            CreateMultipleTasksAsync();
            Console.ReadKey();
        }
        static async Task CreateMultipleTasksAsync()
        {            
        HttpClient client =
                new HttpClient() { MaxResponseContentBufferSize = 1000000 };
            Task<byte[]> download1 =
                client.GetByteArrayAsync("http://msdn.microsoft.com");
            Task<byte[]> download2 =
                client.GetByteArrayAsync("http://msdn.microsoft.com/en-us/library/hh156528(VS.110).aspx");
            Task<byte[]> download3 =
                client.GetByteArrayAsync("http://msdn.microsoft.com/en-us/library/67w7t67f.aspx");
            int length1 = (await download1).Length;
            int length2 = (await download2).Length;
            int length3 = (await download3).Length;
            //#1 there I need results of all 3 downloads (so it will wait for all 3 downloads being completed)
            Console.WriteLine("'r'n'r'nTotal bytes returned:  {0}'r'n", length1 + length2 + length3);
        }
    }
}

和async/wait-way真的更短,更好,因为我所有的代码是紧凑的,所有的在一个方法CreateMultipleTasksAsync和我不应该创建额外的方法和委托它。

但是,如果我想对单个下载的结果做一些事情(独立于其他下载,如#2所示),我需要将一段代码取出到一个单独的方法中,该方法中只有一个await修饰符。

我需要额外的方法,因为我不能这样写代码:

...
            Task<int> download1 =
                ProcessURLAsync("http://msdn.microsoft.com", client);
            Task<int> download2 =
                ProcessURLAsync("http://msdn.microsoft.com/en-us/library/hh156528(VS.110).aspx", client);
            Task<int> download3 =
                ProcessURLAsync("http://msdn.microsoft.com/en-us/library/67w7t67f.aspx", client);
            int length1 = (await download1).Length;
            //#3
            Console.WriteLine("'n{0,-58} {1}", "http://msdn.microsoft.com", length1);
            int length2 = (await download2).Length;
            //#4
            Console.WriteLine("'n{0,-58} {1}", "http://msdn.microsoft.com/en-us/library/hh156528(VS.110).aspx", length2);
            int length3 = (await download3).Length;
            //#5
...

,因为在此代码中,download2的结果总是在处理download1(#3)的结果之后处理(输出到控制台#4),download3的结果总是在处理download1(#3)和download2(#4)的结果之后处理(输出到控制台#5)(不同结果的处理依赖于其他结果的处理)。在这种情况下,输出顺序总是相同的(即使download3比download1更早完成,无论如何#5将显示在#3之后)。

但是,如果我希望#5在#3之前显示,而download3比download1更早完成,我就必须创建一个额外的方法ProcessURLAsync:

using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            CreateMultipleTasksAsync();
            Console.ReadKey();
        }
        static async Task CreateMultipleTasksAsync()
        {         
        HttpClient client =
                new HttpClient() { MaxResponseContentBufferSize = 1000000 };   
            Task<int> download1 =
                ProcessURLAsync("http://msdn.microsoft.com", client);
            Task<int> download2 =
                ProcessURLAsync("http://msdn.microsoft.com/en-us/library/hh156528(VS.110).aspx", client);
            Task<int> download3 =
                ProcessURLAsync("http://msdn.microsoft.com/en-us/library/67w7t67f.aspx", client);
            int length1 = await download1;
            int length2 = await download2;
            int length3 = await download3;
            //#1 there I need results of all 3 downloads (so it will wait for all 3 downloads being completed)
            Console.WriteLine("'r'n'r'nTotal bytes returned:  {0}'r'n", length1 + length2 + length3);
        }
        static async Task<int> ProcessURLAsync(string url, HttpClient client)
        {
            var length = (await client.GetByteArrayAsync(url)).Length;
            //#2 there I need only a result of one current download, independently from other downloads (so it will wait only for current download being completed)
            Console.WriteLine("'n{0,-58} {1}", url, length);
            return length;
        }
    }
}

,在这种情况下,为什么async/wait-way更好不是那么明显,因为我需要创建额外的ProcessURLAsync方法来处理单个下载的结果,所以我的代码很像第一个示例的代码,没有async/await。

如果我们忽略async/wait-way明显优于Thread-way的事实,原因有三个:1)你应该将url作为对象传递给委托,然后将其强制转换为字符串;2)你可以只使用局部变量来存储长度(length1, length2, length3),而不需要创建静态属性程序。长度(因为你不能从委托返回值),3)你不能传递超过1个参数给委托,所以你需要让'HttpClient客户端'静态而不是本地-这是否意味着async/wait-way只有在以下情况下才真正更好:1)你只需要1个并行任务;2)或者你需要多个并行任务,但不需要分别处理它们(只需要一起处理它们)?

如果没有并行任务是相互依赖的,我应该单独处理它们,语法async/await有什么优势吗?

对于程序员来说,async/await语法是否真的比手动线程更好,或者有时它不是那么有用

您的最终代码确实无法与线程相比。使用单线程SynchronizationContext(默认为UI应用程序),ProcessURLAsync调用执行重叠,但不是并行的。唯一的中断点是使用await关键字。

这意味着他们可以安全地访问UI并更新共享的数据结构,而无需额外的同步。与显式线程相比,这可以大大减少代码长度和复杂性。

(注意:正如在注释中提到的,这一行出现在你的线程false等价访问一个共享变量没有同步,因此遭受竞争条件:Program.length =+ length;这是除了等待线程完成之前打印结果的总失败。如果HttpClient client对象不是线程安全的,您可能会遇到其他问题。

这里有几件事。

首先,由于ui与控制台应用程序的同步上下文不同,异步在控制台应用程序中的工作方式可能非常奇怪,请参阅本文。

其次,异步并不一定等同于多线程。如果您执行Task.Run(...)之类的操作,则肯定会在线程池上运行。然而,一个"标准"的异步操作是不一样的。

我的标准例子是这样的:假设你去一家有10个人的餐馆。当服务员经过时,他问的第一个人还没有准备好;然而,另外9个人是。因此,服务员向其他9个人要了他们的菜,然后又回到原来的那个人那里。(这绝对不是的情况,他们会让第二个服务员等待原来的人准备好点餐,这样做可能不会节省太多时间)。这就是async/await的典型工作方式(例外的是,一些Task Parallel库调用,如Thread.Run(…),实际上是在上执行其他线程——在我们的示例中,引入了第二个服务员——所以请确保您检查文档,以确定哪个是哪个)。

请注意,如果您没有在控制台应用程序中使用同步上下文,那么对于实际运行哪个线程的异步方法的保证要少得多,因此在这种情况下,它的行为可能不完全符合您的期望。

实际上,您最终使用哪一个取决于您的任务是否是cpu限制的。如果它不是一个cpu限制的操作(例如,它主要只是等待来自服务器、外部硬件等的结果),使用线程与异步的性能差异可能不会太大——你可以做相当于告诉服务员"回来找我"的事情。但是,对于cpu密集型操作,您可能希望将其放在单独的线程上。

另外,在你发布的代码样本中,似乎没有理由等待每一种情况下操作的结果;如果调用者没有立即需要结果,则不必等待结果。事实上,不等待就"启动"进程可以大大提高性能。当然,需要注意的是,要确保在关闭控制台应用程序之前完成所有任务。

希望这能澄清一点;如果没有,请告诉我,我可以编辑我的答案。