【问题标题】:Is async/await syntax really better than manual threading for a programmer or sometimes it is not so useful?对于程序员来说,async/await 语法真的比手动线程更好吗,或者有时它不是那么有用?
【发布时间】:2017-01-16 19:20:53
【问题描述】:

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

如果我需要 3 个并行任务(下载)并且我想对每次下载的结果进行处理(其中处理(将 SUM 输出到控制台)取决于每次下载,并且我同时需要所有结果,如 #1 所示),我可以使用 Thread 来完成,而无需像这里那样使用 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/await 方式确实更短更好,因为我所有的代码都很紧凑,而且都在一个方法中 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
...

因为在此代码中,总是在处理 download1 (#3) 的结果后处理 download2 的结果(在 #4 处输出到控制台),并且在处理后始终处理 download3 的结果(在 #5 处输出到控制台) download1 (#3) 和 download2 (#4) 的结果(不同结果的处理依赖于其他结果的处理)。并且这种情况下的输出顺序总是相同的(即使 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/await 方式更好并不那么明显,因为我需要创建额外的方法 ProcessURLAsync 来处理单个下载的结果,所以我的代码很像第一个示例的代码,没有异步/等待。

如果我们忽略 async/await-way 明显优于 Thread-way 的事实有 3 个原因 - 1)您应该将 url 作为对象传递给委托,然后将其转换回字符串,2)您只能使用用于存储长度(length1、length2、length3)的局部变量,无需创建静态属性 Program.length(因为您不能从委托返回值),3)您不能将超过 1 个参数传递给委托所以你需要使'HttpClient客户端'静态而不是本地 - 这是否意味着异步/等待方式只有在以下情况下才真正更好:1)你只需要1个并行任务; 2)或者您需要多个并行任务但不需要彼此分开处理(只需要完全处理它们)?

如果没有一个并行任务相互依赖,我应该分别处理它们,那么语法 async/await 有什么优势吗?

【问题讨论】:

  • 大锤比锤子好吗?视情况而定。不要忘记任务不是线程。 blogs.msdn.microsoft.com/benwilli/2015/09/10/…
  • edit您的帖子并提出具体问题。目前还不清楚您想要实现什么...目前您正在比较不正确的(不是线程安全的)代码与并行运行的线程以及对多个异步任务的顺序调用 - 并声称没有理由执行任务...相当令人困惑。
  • 并发线程和等待异步操作是不同的东西。你到底在比较什么?

标签: c# .net multithreading asynchronous async-await


【解决方案1】:

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

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

(注意:如 cmets 中所述,出现在您的线程 false 等效项中的这一行在没有同步的情况下访问共享变量,因此遭受竞争条件:Program.length =+ length; 这是完全等待线程失败的补充在打印结果之前完成。如果HttpClient client 对象不是线程安全的,您可能会遇到其他问题。)

【讨论】:

  • 这是在打印结果之前等待线程完成的完全失败的补充。) - 如果您的意思是我打印出 Program.length 的结果无需等待所有线程完成 - 你是对的,我只是没有测试我的第一个示例,因为它无法编译。现在我看到一个错误。
【解决方案2】:

这里有一些东西。

首先,由于 UI 与控制台应用程序中的同步上下文之间存在差异,异步在控制台应用程序中的工作方式可能非常奇怪,请参阅 this article

其次,异步不一定与多线程相同。如果您执行Task.Run(...) 之类的操作,那肯定会在线程池上运行。但是,“标准”异步操作并不相同。

我对此的标准说明如下:假设你去一家有 10 个人的餐厅。当服务员过来时,他第一个点菜的人还没准备好;然而,其他9人是。因此,服务员向其他 9 人询问他们的订单,然后回到原来的那个人。 (绝对不是他们会让第二个服务员等待原来的人准备好点菜的情况,这样做可能不会节省太多时间)。这就是 async/await 通常的工作方式(例外是一些任务并行库调用,如 Thread.Run(...),实际上 在其他线程上执行 - 在我们的插图中,引入第二个服务员 - 所以一定要检查文件是哪个)。

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

真的,您最终使用哪一个取决于您的任务是否受 CPU 限制。如果它不是受 CPU 限制的操作(即,它主要只是在等待来自服务器、外部硬件等的结果),那么使用线程与异步的性能差异可能不会太显着 - 你可以这样做相当于告诉服务员“回到我身边”。但是,对于 CPU 密集型操作,您可能希望将其放在单独的线程上。

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

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

【讨论】:

    猜你喜欢
    • 2016-05-30
    • 2019-04-25
    • 1970-01-01
    • 1970-01-01
    • 2013-01-12
    • 1970-01-01
    • 2014-03-13
    • 2018-02-06
    • 1970-01-01
    相关资源
    最近更新 更多