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

.Net(C#)异步x并行性能,c#,.net,multithreading,asynchronous,parallel-processing,C#,.net,Multithreading,Asynchronous,Parallel Processing,Net非常有助于异步和并行编程。有一些关于这方面的好文章,比如 但是什么是最好的,在什么情况下?我刚刚创建了一个简单的控制台应用程序来尝试解决这个问题。 看起来async和await的速度更快,但它会消耗更多内存。这是在同一台机器上重复测试三次的平均结果(CPU=AMD FX 8120八核处理器3,1GHz,RAM=16GB): 异步运行时间=230841498667 |内存(MB)=62154 并行运行时间=1079682892667 |内存(MB)=27828 代码在中,非常简单 代码

Net非常有助于异步和并行编程。有一些关于这方面的好文章,比如

但是什么是最好的,在什么情况下?我刚刚创建了一个简单的控制台应用程序来尝试解决这个问题。 看起来async和await的速度更快,但它会消耗更多内存。这是在同一台机器上重复测试三次的平均结果(CPU=AMD FX 8120八核处理器3,1GHz,RAM=16GB):


异步运行时间=230841498667 |内存(MB)=62154


并行运行时间=1079682892667 |内存(MB)=27828


代码在中,非常简单

代码请求网页250次

异步测试是:

    public async static Task AsynchronousTest()
    {
        List<Task<string>> taskList = new List<Task<string>>();

        for (int i = 0; i < TOTAL_REQUEST; i++)
        {
            Task<string> 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<string> 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()
{
    List<string> results = new List<string>();
    Parallel.For(0, TOTAL_REQUEST, i =>
    {
        string html = GetHtml(i);
        lock(results)
            results.Add(html);
    });
}
你看到的报道时间。我发现非常令人惊讶的是
async
比线程方法快得多。事实证明,这是因为缓存。 让我们把它关掉:

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服务器):

[路由(“api/[控制器]”)]
公共类值控制器:控制器
{
[HttpGet]
公共异步任务Get()
{
等待任务。延迟(1000);
返回新字符串[]{“value1”,“value2”};
}
}
我在一些不同的上下文中运行了测试,结果如下表所示:

+--------------------------------------+------------+------------------+------------------+
||请求| 100 | 500|
+---------------------------------------+------------+------------------+------------------+
|同步|已用时间| 1015830302 | 5076880514|
||内存(MB)12490666667 | 186933333|
||螺纹| 6 | 667|
|异步(WebRequest)|已用时间| 90738118667 | 253127221|
||内存(MB)| 18530666667 | 20276|
||螺纹| 27 | 426667|
|异步(Httpclient)|已用时间| 12210176 | 16143776333|
||内存(MB)| 20880 | 302933333|
||螺纹| 28 | 29|
|异步(Httpclient内存泄漏)|已用时间| 14515700667 | 14114106|
||内存(MB)| 2133466667 | 320706667|
||螺纹| 28333333 | 29|
|并行(WebRequest)|经过时间| 121617405333 | 31662035433|
||内存(MB)124413333333 | 20004|
||螺纹| 25 | 37|
|并行(HttpClient)|运行时间| 159617157667 | 318756134|
||内存(MB)| 19612 | 148346667|
||螺纹| 363333333 | 41666667|
+---------------------------------------+------------+------------------+------------------+


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

您的并行测试可能不应该使用异步方法-这似乎是错误的。我可以想象一个适当的并行测试会比异步测试更快(即使只是稍微快一点)。任务测试使用了更多的内存,因为您要存储250个任务,直到它们全部完成,包括下载的HTML。并行测试获取HTML,然后立即将其丢弃,并且只会在一个类型上启动几个线程(通常是计算机上的内核数量)。并行测试实际上不使用异步,这是为了模拟同步并行x异步编程。您的测试技术完全错误:您不能期望服务器允许您并行连接50次(例如)。您应该只测试本地的东西,可以通过您的计算机单独完成,然后再次,它仍然是错误的。你不能说“并行更快”(或更慢),因为这完全取决于每种情况。但实际上它使用的是
async
框架。你在不必要地旋转任务并阻塞它们。要正确比较线程与异步线程,不应强制并行测试将
用作
public static void ParallelTest()
{
    Parallel.For(0, TOTAL_REQUEST, i =>
    {
        string html = GetHtml(i);
        File.WriteAllText($"SomeFile {i}.txt", html);
    });
}
using (HttpClient client = new HttpClient(new WebRequestHandler { CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache) }))
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
[Route("api/[controller]")]
public class ValuesController : Controller
{
    [HttpGet]
    public async Task<IEnumerable<string>> Get()
    {
        await Task.Delay(1000);

        return new string[] { "value1", "value2" };
    }
}