对于程序员来说,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有什么优势吗?
您的最终代码确实无法与线程相比。使用单线程SynchronizationContext(默认为UI应用程序),ProcessURLAsync
调用执行重叠,但不是并行的。唯一的中断点是使用await
关键字。
(注意:正如在注释中提到的,这一行出现在你的线程false等价访问一个共享变量没有同步,因此遭受竞争条件:Program.length =+ length;
这是除了等待线程完成之前打印结果的总失败。如果HttpClient client
对象不是线程安全的,您可能会遇到其他问题。
这里有几件事。
首先,由于ui与控制台应用程序的同步上下文不同,异步在控制台应用程序中的工作方式可能非常奇怪,请参阅本文。
其次,异步并不一定等同于多线程。如果您执行Task.Run(...)
之类的操作,则肯定会在线程池上运行。然而,一个"标准"的异步操作是不一样的。
请注意,如果您没有在控制台应用程序中使用同步上下文,那么对于实际运行哪个线程的异步方法的保证要少得多,因此在这种情况下,它的行为可能不完全符合您的期望。
实际上,您最终使用哪一个取决于您的任务是否是cpu限制的。如果它不是一个cpu限制的操作(例如,它主要只是等待来自服务器、外部硬件等的结果),使用线程与异步的性能差异可能不会太大——你可以做相当于告诉服务员"回来找我"的事情。但是,对于cpu密集型操作,您可能希望将其放在单独的线程上。
另外,在你发布的代码样本中,似乎没有理由等待每一种情况下操作的结果;如果调用者没有立即需要结果,则不必等待结果。事实上,不等待就"启动"进程可以大大提高性能。当然,需要注意的是,要确保在关闭控制台应用程序之前完成所有任务。
希望这能澄清一点;如果没有,请告诉我,我可以编辑我的答案。