【问题标题】:async HttpWebRequest vs sync in multithreaded app多线程应用程序中的异步 HttpWebRequest 与同步
【发布时间】:2015-02-06 14:18:44
【问题描述】:

我有一个应用程序,我需要一次发出很多 httpWebRequest 并尽可能快地获得每个应用程序的响应。 实际上,我不需要快速获得所有响应,我需要尽可能快地获得每个响应。我的意思是在我发送每个请求和得到响应之间必须有最短时间。

我阅读了很多关于 async/await 技术的文章并选择了这个解决方案:

    private static void Main()
    {
        ServicePointManager.DefaultConnectionLimit = 100;
        ServicePointManager.Expect100Continue = false;
        ServicePointManager.UseNagleAlgorithm = false;

        ProcessAsync("https://www.google.ru");

        Console.ReadKey();
    }

    private static void ProcessAsync(string url)
    {
        for (var i = 1; i <= 100; i++)
            Task.Factory.StartNew(async () => await ProcessInternalAsync(url));
    }

    private static async Task ProcessInternalAsync(string url)
    {
        var request = GetRequest(url);

        var sw = Stopwatch.StartNew();
        await GetStringContentAsync(request);
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }

    public static async Task<string> GetStringContentAsync(HttpWebRequest webRequest)
    {
        using (var response = (HttpWebResponse) await webRequest.GetResponseAsync()
                                                                .ConfigureAwait(false))
        {
            var content = await GetStringContentFromResponseAsync(response)
                .ConfigureAwait(false);
            return content;
        }
    }

    private static async Task<string> GetStringContentFromResponseAsync(HttpWebResponse response)
    {
        using (var responseStream = GetStreamForResponse(response))
        {
            if (responseStream == null)
                return null;
            using (var streamReader = new StreamReader(responseStream))
            {
                var content = await streamReader.ReadToEndAsync()
                                                .ConfigureAwait(false);
                return content;
            }
        }
    }    

这可行,但速度很慢..(每个请求 500-1600 毫秒)

所以我尝试了没有异步/等待的“简单”解决方案:

    private static void Main()
    {
        ServicePointManager.DefaultConnectionLimit = 100;
        ServicePointManager.Expect100Continue = false;
        ServicePointManager.UseNagleAlgorithm = false;

        Process("https://www.google.ru");

        Console.ReadKey();
    }

    private static void Process(string url)
    {
        for (var i = 1; i <= 100; i++)
            Task.Factory.StartNew(() => ProcessInternal(url));
    }

    private static void ProcessInternal(string url)
    {
        var request = GetRequest(url);

        var sw = Stopwatch.StartNew();
        GetStringContent(request);
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }

    public static string GetStringContent(HttpWebRequest webRequest)
    {
        using (var response = (HttpWebResponse) webRequest.GetResponse())
        {
            var content = GetStringContentFromResponse(response);
            return content;
        }
    }

    private static string GetStringContentFromResponse(HttpWebResponse response)
    {
        using (var responseStream = GetStreamForResponse(response))
        {
            if (responseStream == null)
                return null;
            using (var streamReader = new StreamReader(responseStream))
            {
                var content = streamReader.ReadToEnd();
                return content;
            }
        }
    }    

而且它的运行速度要快得多! (每个请求 55-180 毫秒)。

这里有一些其他的方法:

    private static HttpWebRequest GetRequest(string url)
    {
        var request = (HttpWebRequest) WebRequest.Create(url);
        request.Timeout = 15000;
        request.Proxy = null;
        request.Headers.Add("Accept-Encoding", "gzip,deflate");

        return request;
    }

    private static Stream GetStreamForResponse(HttpWebResponse webResponse)
    {
        var responseStream = webResponse.GetResponseStream();
        if (responseStream == null)
            return null;

        Stream stream;
        switch (webResponse.ContentEncoding.ToUpperInvariant())
        {
            case "GZIP":
                stream = new GZipStream(responseStream, CompressionMode.Decompress);
                break;
            case "DEFLATE":
                stream = new DeflateStream(responseStream, CompressionMode.Decompress);
                break;
            default:
                stream = responseStream;
                break;
        }
        return stream;
    }

所以我的问题是:为什么差别这么大?我认为当我将 await 与 I/O 操作一起使用时 - 这是一种好方法,也是执行 HTTP 请求的最佳方法。但在实践中并不完全正确。

在没有 async/await 的解决方案中,我们在等待响应时有线程阻塞,如果它不是 Google 并且将是具有长时间运行操作的服务器,则此请求将阻塞我的所有应用程序。

可能有更好的方法来解决我的问题?

附: 这个“魔法”: ServicePointManager.Expect100Continue = false; ServicePointManager.UseNagleAlgorithm = false;
只是来自互联网的一些建议,我不知道它是如何工作的..

更新 感谢Servy,我终于明白了我在测量方面的错误。现在 Process 方法如下所示:

    private static void Process(string url)
    {
        var taskCollection = new List<Task>();
        for (var i = 1; i <= 100; i++)
        {
            var sw = Stopwatch.StartNew();
            taskCollection.Add(Task.Factory.StartNew(() =>
                                                     {
                                                         ProcessInternal(url);
                                                         sw.Stop();
                                                         Console.WriteLine(sw.ElapsedMilliseconds);
                                                     }));
        }
        Task.WaitAll(taskCollection.ToArray());
    }

现在第二个解决方案显示其毫秒数(请求为 300-1000 毫秒)。但无论如何,它比 async/await 更快。 async/await 方法一定要慢吗?

【问题讨论】:

    标签: c# multithreading asynchronous httpwebrequest httpwebresponse


    【解决方案1】:

    用秒表测量整个操作的时间,你会发现时间会接近很多。

    基本上,您会更快地在异步版本中启动秒表,这就是为什么它们似乎需要更长的时间。

    在异步解决方案中,您同步启动每个操作,然后异步等待它们全部完成。在这种情况下,秒表几乎都是在应用程序启动时立即启动的。

    对于您的同步解决方案,您甚至不会启动秒表,直到每个任务都被安排在线程池线程中运行,被分配给一个线程,并且实际上是从该线程运行的。大部分的等待都将发生在这里,你甚至还没有开始你的秒表。

    【讨论】:

    • 感谢您的回答。你是对的,我在开始任务时失去了一些时间,但无论如何差异是如此之大,以至于问题不在 5-10 毫秒内。我在 GetStringContent 和 GetStringContentAsync 中移动了秒表(这可能有助于衡量更公平),但根本没有差异。
    • 你怎么看 - 我需要在哪里放置秒表来测量异步解决方案中的请求时间?
    • @ЮрийБржозовский 我从一开始就说明了你应该做什么;测量整个程序的时间,而不是单个操作。
    • 我理解你,但我的问题不是整个程序运行缓慢。问题是当我使用异步/等待时单个请求很慢。如何更公平地衡量单个请求?
    • @ЮрийБржозовский 您可以在调用StartNew 之前从Process 内启动每个计时器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-17
    • 2012-10-22
    • 1970-01-01
    相关资源
    最近更新 更多