.Net(C#)异步x并行性能

.Net对异步和并行编程很有帮助。 有一些关于这方面的好文章

但什么是最好的和在哪种情况下? 我刚刚创建了一个简单的控制台应用程序来试图找出答案。 它看起来像异步和等待更快,但它消耗更多的内存。 这是在同一台机器上重复测试三次的平均结果(CPU = AMD FX 8120八核处理器3,1GHz和RAM = 16GB):


异步经过时间= 23,0841498667 | 记忆(MB)= 62154


平行经过时间= 107,9682892667 | 记忆(MB)= 27828


代码在GitHub中 ,非常简单。

该代码请求网页250次。

异步测试是:

public async static Task AsynchronousTest() { List<Task> taskList = new List<Task>(); for (int i = 0; i < TOTAL_REQUEST; i++) { Task taskGetHtmlAsync = GetHtmlAsync(i); taskList.Add(taskGetHtmlAsync); } await Task.WhenAll(taskList); //Trying to free memory taskList.ForEach(t => t.Dispose()); taskList.Clear(); taskList = null; GC.Collect(); } public async static Task GetHtmlAsync(int i) { string url = GetUrl(i); using (HttpClient client = new HttpClient()) { string html; html = await client.GetStringAsync(url); Trace.WriteLine(string.Format("{0} - OK (Html Length {1})", i + 1, html.Length)); return html; } } 

并行测试是这样的:

  public static void ParallelTest() { Parallel.For(0, TOTAL_REQUEST, i => { string html = GetHtml(i); }); } public static string GetHtml(int i) { string html = null; string url = GetUrl(i); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { using (Stream receiveStream = response.GetResponseStream()) { using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8)) { html = readStream.ReadToEnd(); } } } Trace.WriteLine(string.Format("{0} - OK (Html Length {1})", i + 1, html.Length)); return html; } 

那么,有没有办法提高async / await方法的内存性能?

它使用更多的内存,因为你将任务和结果保存在内存中,而在并行测试中,你不是。

为了更加等效,并行测试可能如下所示:

 public static void ParallelTest() { List results = new List(); Parallel.For(0, TOTAL_REQUEST, i => { string html = GetHtml(i); lock(results) results.Add(html); }); } 

我希望并行测试仍能消耗更少的内存,但不会像现在这样多。

如果您想下载结果,并且只在所有请求完成后处理它们,那么您无法做很多事情来加快速度。

但是,如果您能够立即处理结果,那么您处于更好的位置。 以此为例:

 public async static Task AsynchronousTest() { for (int i = 0; i < TOTAL_REQUEST; i++) { var result = await GetHtmlAsync(i); File.WriteAllText($"SomeFile {i}.txt", result); } } 

您的内存消耗将大幅减少,因为您不再将所有请求保留在内存中; 你正在处理它们并随时丢弃它们。

当然,并行等价物可能是:

 public static void ParallelTest() { Parallel.For(0, TOTAL_REQUEST, i => { string html = GetHtml(i); File.WriteAllText($"SomeFile {i}.txt", html); }); } 

在您所看到的报告时间内。 我发现非常令人惊讶的是, async比线程方法快得多。 事实certificate,这是因为缓存。 我们把它关掉:

GetHtmlAsync

 using (HttpClient client = new HttpClient(new WebRequestHandler { CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache) })) 

GetHtml

 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); 

线程速度提高2-3倍(10项)。 听起来更合理一些。 问题是, async通常用于在后台运行进程,以便不阻止UI线程。 但是,对于线程旋转起来更为明智,通常是计算机上的核心数量(+ - 其他因素)。

结论:

线程可能是更好的方法。 这是工作的更好工具(你知道你主要受限于Net / IO,而不是CPU)。 由于async开销以及存储结果,您会看到更高的内存数,我在上面提到过。

我发现这个链接显示短生命对象HttpClient会留下内存泄漏,所以这就是HttpClient在for循环中使用的问题。

我还改进了测试,使用本地Asp.net 5 webapi异步方法来回答请求(这样,测试不会因任何原因对Web服务器产生影响):

 [Route("api/[controller]")] public class ValuesController : Controller { [HttpGet] public async Task> Get() { await Task.Delay(1000); return new string[] { "value1", "value2" }; } } 

我已在一些不同的上下文中运行测试,结果在此表中:

+--------------------------------------+------------+------------------+------------------+ | | Request | 100 | 500 | +---------------------------------------+------------+------------------+------------------+ | synchronous | Elapsed(s) | 101,5830302 | 507,6880514 | | | Memory(MB) | 15390,6666666667 | 18693,3333333333 | | | Threads | 6 | 6,6666666667 | | asynchronous (WebRequest) | Elapsed(s) | 9,0738118667 | 25,3127221 | | | Memory(MB) | 18530,6666666667 | 20276 | | | Threads | 27 | 42,6666666667 | | asynchronous (Httpclient) | Elapsed(s) | 1,2210176 | 1,6143776333 | | | Memory(MB) | 20880 | 30293,3333333333 | | | Threads | 28 | 29 | | asynchronous (Httpclient Memory Leak) | Elapsed(s) | 1,4515700667 | 1,4114106 | | | Memory(MB) | 21334,6666666667 | 32070,6666666667 | | | Threads | 28,3333333333 | 29 | | parallel (WebRequest) | Elapsed(s) | 12,1617405333 | 31,6620354333 | | | Memory(MB) | 18441,3333333333 | 20004 | | | Threads | 25 | 37 | | parallel (HttpClient) | Elapsed(s) | 15,9617157667 | 31,8756134 | | | Memory(MB) | 19612 | 14834,6666666667 | | | Threads | 36,3333333333 | 41,6666666667 | +---------------------------------------+------------+------------------+------------------+

最快的结果是使用异步和HttpClient。 具有良好内存的良好性能是使用并行和HttpClient。