async HttpWebRequest vs sync in multithreaded app

本文关键字:multithreaded app in sync HttpWebRequest vs async | 更新日期: 2023-09-27 18:33:30

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

我阅读了许多关于异步/等待技术的文章,并选择了这个解决方案:

    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;只是来自互联网的一些建议,我不知道它是如何工作的。

更新多亏了塞尔维,我终于明白了我在测量方面的错误。现在处理方法如下所示:

    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 HttpWebRequest vs sync in multithreaded app

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

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

在异步解决方案中,同步启动每个操作,然后异步等待所有操作完成。 在这种情况下,当应用程序启动时,秒表几乎都会立即启动。

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