等待在此函数中做什么
本文关键字:什么 函数 等待 | 更新日期: 2023-09-27 18:37:06
我以为我理解C#
async-await
模式,但今天我发现我真的不明白。
在像这样的简单代码片段中。我已经System.Net.ServicePointManager.DefaultConnectionLimit = 1000;
定义。
public static async Task Test()
{
HttpClient client = new HttpClient();
string str;
for (int i = 1000; i <= 1100; i++)
str = await client.GetStringAsync($"https://en.wikipedia.org/wiki/{i1}");
}
await
在这里做什么?最初我认为,由于这是异步等待模式,这意味着基本上 HttpClient 将以多线程方式启动所有 HTTP GET 调用,即基本上应该一次获取所有 URL。
但是当我使用 Fiddler 来分析行为时,我发现它确实按顺序获取 URL。
我需要将其更改为此以使其工作:
public static async Task<int> Test()
{
int ret = 0;
HttpClient client = new HttpClient();
List<Task> taskList = new List<Task>();
for (int i = 1000; i <= 1100; i++)
{
var i1 = i;
var task = Task.Run(() => client.GetStringAsync($"https://en.wikipedia.org/wiki/{i1}"));
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
return ret;
}
这次是并行获取 URL。那么await
关键字在第一个代码片段中到底做了什么呢?
它等待 HTTP 请求的完成。代码仅在每个请求完成后恢复(for
迭代...)。
您的第二个版本之所以有效,正是因为它在启动以下任务之前不会等待每个任务完成,而只会在所有任务启动后等待所有任务完成。
async-await 的用处是允许调用函数在异步函数等待时继续执行其他操作,而不是阻止调用函数直到完成的同步("正常")函数。
await
是异步等待。它不是阻止调用,并允许方法的调用方继续。await
后方法内的其余代码将在返回Task
完成后执行。
在代码的第一个版本中,允许调用方继续。但是,循环的每次迭代都将等到GetStringAsync
返回的Task
完成。这具有按顺序下载每个 URL 的效果,而不是并发下载。
请注意,代码的第二个版本不是异步的,因为它使用线程并行执行工作。
如果它是异步的,它将仅使用一个线程检索网页内容,但仍同时检索。
像这样的东西(未经测试):
public static async Task<int> Test()
{
int ret = 0;
HttpClient client = new HttpClient();
List<Task> taskList = new List<Task>();
for (int i = 1000; i <= 1100; i++)
{
var i1 = i;
taskList.Add(client.GetStringAsync($"https://en.wikipedia.org/wiki/{i1}"));
}
await Task.WhenAll(taskList.ToArray());
return ret;
}
在这里,我们异步启动任务并将它们添加到 taskList
.这些任务是非阻塞性的,将在下载完成并检索字符串时完成。注意调用Task.WhenAll
而不是Task.WaitAll
:前者是异步和非阻塞的,后者是同步和阻塞的。这意味着,在await
,这个Test()
方法的调用者将收到返回的Task<int>
:但在下载所有字符串之前,任务将不完整。
这就是迫使async
/await
在整个堆栈中扩散的原因。一旦最底部的调用是异步的,只有当其他调用方也是异步的时,它才有意义。否则,您将被迫通过Task.Run()
调用或类似方式创建线程。
根据 msdn 文档
await 运算符应用于异步方法中的任务,以暂停方法的执行,直到等待的任务完成。该任务代表正在进行的工作。
这意味着 await 运算符会阻止 for 循环的执行,直到它从服务器获得响应,使其按顺序排列。
您可以做的是创建所有任务(以便它开始执行),然后等待所有任务。
这是另一个StackOverflow问题的示例
public IEnumerable<TContent> DownloadContentFromUrls<TContent>(IEnumerable<string> urls)
{
var queue = new ConcurrentQueue<TContent>();
using (var client = new HttpClient())
{
Task.WaitAll(urls.Select(url =>
{
return client.GetAsync(url).ContinueWith(response =>
{
var content = JsonConvert.
DeserializeObject<IEnumerable<TContent>>(
response.Result.Content.ReadAsStringAsync().Result);
foreach (var c in content)
queue.Enqueue(c);
});
}).ToArray());
}
return queue;
}
msdn 中也有很好的文章解释了如何使用 await 发出并行请求。
编辑:
正如@GaryMcLeanHall注释中指出的那样,您可以将Task.WaitAll
更改为await Task.WhenAll
并添加 async
修饰符以使方法异步返回
这是另一篇 msdn 文章,它选择了第一篇中的示例并添加了 WhenAll
的使用。