我认为我理解async-await
中的C#
模式,但今天我发现我确实没有。
在这样一个简单的代码片段中。我已经定义了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
在这做什么?最初我认为因为这是在async-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
关键字在第一个代码段中真正做了什么?
答案 0 :(得分:9)
等待 完成HTTP请求。只有在每个请求完成后,代码才会恢复(for
迭代...)。
您的第二个版本正是因为在启动以下任务之前没有等待完成每项任务,并且只在所有任务完成后等待所有任务完成。< / p>
async-await对于允许调用函数在异步函数等待时继续执行其他操作非常有用,而不是同步(&#34;普通&#34;)函数阻止调用函数直到完成。
答案 1 :(得分:4)
await
是异步等待。它不是阻塞调用,并允许您的方法的调用者继续。当await
返回完成后,将在Task
之后执行方法内的其余代码。
在代码的第一个版本中,您允许呼叫者继续。但是,循环的每次迭代都将等到Task
返回的GetStringAsync
完成。这具有顺序下载每个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()
电话或某些人创建一个线程。
答案 2 :(得分:1)
根据msdn documentation
await运算符应用于异步方法中的任务,以暂停方法的执行,直到等待的任务完成。该任务代表了正在进行的工作。
这意味着await运算符会阻止for循环的执行,直到它从服务器获得响应,使其成为顺序。
你可以做的是创建所有任务(以便它开始执行),然后等待所有任务。
以下来自另一个StackOverflow question的示例
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中还有很好的article解释了如何通过等待发出并行请求。
编辑:
正如@GaryMcLeanHall在评论中指出的那样,您可以将Task.WaitAll
更改为await Task.WhenAll
并添加async
修饰符以使该方法异步返回
这是另一个msdn article,它选择了第一个中的示例并添加WhenAll
的使用。