0

我有一个应用程序需要向第三方 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

4 回答 4

5

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

也就是说,您应该设置ServicePointManager.DefaultConnectionLimitint.MaxValue. 此外,使用端到端async方法(即,StreamReader.ReadToEndAsync)——或者更好的是,使用HttpClient专为asyncHTTP 设计的。

于 2013-10-29T20:30:35.003 回答
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();
    }
}
于 2013-10-29T20:18:23.707 回答
0

WebRequest.GetResponseAsync()在我的测试中,使用该方法处理 3 个并行请求会更快。

对于大型请求、许多请求(3 并不多)以及来自不同网站的请求,它应该更加明显。

你得到的确切结果是什么?在您的问题中,您将 TimeSpan 转换为字符串并将其称为毫秒,但您实际上并没有计算毫秒。它显示标准 TimeSpan.ToString ,它将显示几分之一秒。

于 2013-10-29T20:18:40.653 回答
-1

看起来这个问题更多的是环境问题。一旦我将代码移到不同网络上的另一台机器上,结果就更符合我的预期。

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

于 2013-10-29T21:23:37.390 回答