为什么异步WebRequests的实现比同步实现慢?

时间:2013-10-29 20:06:26

标签: c# performance asynchronous httpwebrequest async-await

我的应用程序需要向第三方REST服务发出许多请求。我认为修改应用程序的这一部分以异步方式发出请求可能会加快速度,所以我编写了一个POC控制台应用程序来测试。

令我惊讶的是,异步代码的完成时间几乎是同步版本的两倍。我只是做错了吗?

async static void LoadUrlsAsync() 
{
    var startTime = DateTime.Now;
    Console.WriteLine("LoadUrlsAsync Start - {0}", startTime);


    var numberOfRequest = 3;
    var tasks = new List<Task<string>>();

    for (int i = 0; i < numberOfRequest; i++)
    {
        var request = WebRequest.Create(@"http://www.google.com/images/srpr/logo11w.png") as HttpWebRequest;
        request.Method = "GET";

        var task = LoadUrlAsync(request);
        tasks.Add(task);
    }

    var results = await Task.WhenAll(tasks);

    var stopTime = DateTime.Now;
    var duration = (stopTime - startTime);
    Console.WriteLine("LoadUrlsAsync Complete - {0}", stopTime);
    Console.WriteLine("LoadUrlsAsync Duration - {0}ms", duration);
}

async static Task<string> LoadUrlAsync(WebRequest request)
{
    string value = string.Empty;
    using (var response = await request.GetResponseAsync())
    using (var responseStream = response.GetResponseStream())
    using (var reader = new StreamReader(responseStream))
    {
        value = reader.ReadToEnd();
        Console.WriteLine("{0} - Bytes: {1}", request.RequestUri, value.Length);
    }

    return value;
}

注意: 我还尝试在app.config中设置 maxconnections = 100 ,以尝试消除来自system.net连接池的限制。此设置似乎不会对性能产生影响。

  <system.net>
    <connectionManagement>
      <add address="*" maxconnection="100" />
    </connectionManagement>
  </system.net>

4 个答案:

答案 0 :(得分:5)

首先,尽量避免使用微基准测试。当您的代码时间差异被网络条件淹没时,您的结果将失去意义。

也就是说,您应该将ServicePointManager.DefaultConnectionLimit设置为int.MaxValue。此外,使用端到端async方法(即StreamReader.ReadToEndAsync) - 甚至更好,使用专为HttpClient HTTP设计的async

答案 1 :(得分:2)

随着线程数量的增加,异步版本变得更快。我不确定,但我的猜测是你绕过了设置线程的成本。当您通过此阈值时,异步版本变得更好。尝试50或甚至500个请求,您应该看到异步更快。这就是我的成功方式。

500 Async Requests:  11.133 seconds
500 Sync Requests:   18.136 seconds

如果您只有~3个电话,那么我建议避免异步。这是我以前测试的内容:

public class SeperateClass
{
    static int numberOfRequest = 500;
    public async static void LoadUrlsAsync()
    {
        var startTime = DateTime.Now;
        Console.WriteLine("LoadUrlsAsync Start - {0}", startTime);

        var tasks = new List<Task<string>>();

        for (int i = 0; i < numberOfRequest; i++)
        {
            var request = WebRequest.Create(@"http://www.google.com/images/srpr/logo11w.png") as HttpWebRequest;
            request.Method = "GET";

            var task = LoadUrlAsync(request);
            tasks.Add(task);
        }

        var results = await Task.WhenAll(tasks);

        var stopTime = DateTime.Now;
        var duration = (stopTime - startTime);
        Console.WriteLine("LoadUrlsAsync Complete - {0}", stopTime);
        Console.WriteLine("LoadUrlsAsync Duration - {0}ms", duration);
    }

    async static Task<string> LoadUrlAsync(WebRequest request)
    {
        string value = string.Empty;
        using (var response = await request.GetResponseAsync())
        using (var responseStream = response.GetResponseStream())
        using (var reader = new StreamReader(responseStream))
        {
            value = reader.ReadToEnd();
            Console.WriteLine("{0} - Bytes: {1}", request.RequestUri, value.Length);
        }

        return value;
    }
}

public class SeperateClassSync
{
    static int numberOfRequest = 500;
    public async static void LoadUrlsSync()
    {
        var startTime = DateTime.Now;
        Console.WriteLine("LoadUrlsSync Start - {0}", startTime);

        var tasks = new List<Task<string>>();

        for (int i = 0; i < numberOfRequest; i++)
        {
            var request = WebRequest.Create(@"http://www.google.com/images/srpr/logo11w.png") as HttpWebRequest;
            request.Method = "GET";

            var task = LoadUrlSync(request);
            tasks.Add(task);
        }

        var results = await Task.WhenAll(tasks);

        var stopTime = DateTime.Now;
        var duration = (stopTime - startTime);
        Console.WriteLine("LoadUrlsSync Complete - {0}", stopTime);
        Console.WriteLine("LoadUrlsSync Duration - {0}ms", duration);
    }

    async static Task<string> LoadUrlSync(WebRequest request)
    {
        string value = string.Empty;
        using (var response = request.GetResponse())//Still async FW, just changed to Sync call here
        using (var responseStream = response.GetResponseStream())
        using (var reader = new StreamReader(responseStream))
        {
            value = reader.ReadToEnd();
            Console.WriteLine("{0} - Bytes: {1}", request.RequestUri, value.Length);
        }

        return value;
    }
}

class Program
{
    static void Main(string[] args)
    {
        SeperateClass.LoadUrlsAsync();
        Console.ReadLine();//record result and run again

        SeperateClassSync.LoadUrlsSync();
        Console.ReadLine();
    }
}

答案 2 :(得分:0)

在我的测试中,对{3个并行请求使用WebRequest.GetResponseAsync()方法的速度更快。

对于大量请求,许多请求(3个并不多)以及来自不同网站的请求,它应该更加引人注目。

您获得的确切结果是什么?在您的问题中,您正在将TimeSpan转换为字符串并将其称为毫秒,但您实际上并未计算毫秒数。它显示标准的TimeSpan.ToString,它将显示一小段时间。

答案 3 :(得分:-1)

看来问题更多的是环境问题而不是其他问题。一旦我将代码移动到另一个网络上的另一台机器上,结果就更符合我的期望。

原始异步代码实际上比同步版本执行得更快。这有助于我确保在没有预期的性能提升的情况下,我不会为我们的应用程序引入额外的复杂性。