我的多线程 HttpClient 有啥问题吗?

Posted

技术标签:

【中文标题】我的多线程 HttpClient 有啥问题吗?【英文标题】:Are there any issues with my multithreaded HttpClient?我的多线程 HttpClient 有什么问题吗? 【发布时间】:2016-01-13 07:28:34 【问题描述】:

所以在涉足 Java 和 HttpClient 之后,我决定过渡到 C# 以尝试在提高速度的同时降低内存使用量。我一直在阅读这里的成员提供的大量关于异步与多线程的文章。看来多线程将是更好的方向。

我的程序将访问服务器并一遍又一遍地发送相同的请求,直到检索到 200 代码。原因是因为在高峰时段交通非常繁忙。它使服务器很难到达并引发 5xx 错误。代码非常简单,我还不想添加循环以帮助查看者简化。

我觉得我正朝着正确的方向前进,但我想与社区联系以改掉任何不良习惯。再次感谢您的阅读。

版主注意:我要求检查我的代码是否存在差异,我很清楚多线程 HttpClient 是可以的。

using System;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Threading;

namespace MF

    class MainClass
    
        public static HttpClient client = new HttpClient();
        static string url = "http://www.website.com";

        public static void getSession() 
            StringContent queryString = new StringContent("json:here");

            // Send a request asynchronously continue when complete
            var result = client.PostAsync(new Uri(url), queryString).Result;

            // Check for success or throw exception
            string resultContent = result.Content.ReadAsStringAsync().Result;

            Console.WriteLine(resultContent);
        
        public static void Run() 
        
            getSession ();
          // Not yet implemented yet..
          //doMore ();
        

        public static void Main (string[] args)
        
            Console.WriteLine ("Welcome!");

            // Create the threads
            ThreadStart threadref1 = new ThreadStart(Run);
            ThreadStart threadref2 = new ThreadStart(Run);
            Console.WriteLine("In the main: Creating the threads...");
            Thread Thread1 = new Thread(threadref1);
            Thread Thread2 = new Thread(threadref1);

            // Start the thread
            Thread1.Start();
            Thread2.Start();
        
    

另外,我不确定这是否重要,但我正在我的 MacBook Pro 上运行它,并计划在我的 BeagleBoard 上运行它。

【问题讨论】:

getSession 绝对应该是@​​987654324@。调用异步方法,例如PostAsync,并等待结果,是不可行的;不要在线程之间共享HttpClient;不要手动创建单独的线程,只需调用async方法N次创建N个任务。 文档声明它不是线程安全的。您可以为每个请求创建 HttpClient 的新实例。如果您更喜欢异步而不是多线程,我希望您的应用程序能够很好地扩展。而不是使用 Result 属性,等待任务。 Is HttpClient safe to use concurrently?的可能重复 真的吗?我读了另一个文档,说它是 Thread 安全的,我一定读过一篇不可信的文章。 我用小待办事项列表更新了答案,如果你也想要答案中的伪代码,请告诉我 【参考方案1】:

如果我是你,我会这样做。我会尽可能多地利用异步,因为它比使用线程更有效(您很可能不必一直进行上下文切换,这很昂贵)。

class MainClass

    public static HttpClient client = new HttpClient();
    static string url = "http://www.website.com";

    public static async Task getSessionAsync()
    
        StringContent queryString = new StringContent("json:here");

        // Send a request asynchronously continue when complete
        using (HttpResponseMessage result = await client.PostAsync(url, queryString))
        
            // Check for success or throw exception
            string resultContent = await result.Content.ReadAsStringAsync();
            Console.WriteLine(resultContent);
        
    

    public static async Task RunAsync()
    
        await getSessionAsync();
        // Not yet implemented yet..
        //doMore ();
    

    public static void Main(string[] args)
    
        Console.WriteLine("Welcome!");

        const int parallelRequests = 5;
        // Send the request X times in parallel
        Task.WhenAll(Enumerable.Range(1, parallelRequests).Select(i => RunAsync())).GetAwaiter().GetResult();

        // It would be better to do Task.WhenAny() in a while loop until one of the task succeeds
        // We could add cancellation of other tasks once we get a successful response
    

请注意,我确实同意@Damien_The_Unbeliever:如果服务器在重负载下出现问题,您不应该添加不必要的负载(执行 X 次相同的请求)并导致服务器问题。理想情况下,您会修复服务器代码,但我可以理解它不是您的。

【讨论】:

这是完美的回应,这正是我所希望的!我确实意识到通过添加更多请求向服务器添加不必要的负载是很愚蠢的,但这是无法通过服务器端修复的。这超出了我的控制范围。 上面的代码对我来说已经运行得更快了,我将最后一行更改为 Task.WhenAll(RunAsync(), RunAsync()).GetAwaiter().GetResult();考虑到我也想执行 doMore(); getSessionAsync() 之后;【参考方案2】:

以下方法是线程安全的:

CancelPendingRequests
DeleteAsync
GetAsync
GetByteArrayAsync
GetStreamAsync
GetStringAsync
PostAsync
PutAsync
SendAsync

更多细节:

Is HttpClient safe to use concurrently? http://blogs.msdn.com/b/henrikn/archive/2012/08/07/httpclient-httpclienthandler-and-httpwebrequesthandler.aspx

【讨论】:

我相信我在我的代码中使用了 PostAsync 和 GetAsync。 @CO 那就没事了。 嗯,上面发布的 msdn 说不值得冒险。有什么风险? @CO 是出现在 MSDN 上的标准文本。并非 HttpClient 的所有公共方法或属性都是线程安全的(您不应该从多个线程更改 DefaultRequestHeads),但实际上发出请求的方法或属性可以安全地并发使用。 HttpClient 的设计者之一 Henrik F Nielsen 在一篇文章中写道,它是为并发使用而设计的:blogs.msdn.com/b/henrikn/archive/2012/08/07/…【参考方案3】:

弄乱标题不是线程安全的。

例如,将 OAuth 访问令牌换成新的不是线程安全的。现在使用 Xamarin 移动应用程序面对这个问题。我有一个崩溃报告,其中一个线程正在修改标头,而另一个线程正在尝试请求。

【讨论】:

【参考方案4】:

阅读HttpClient的文档:

此类型的任何公共静态(在 Visual Basic 中为共享)成员都是线程安全的。不保证任何实例成员都是线程安全的。

不要冒险。有一个单独的 HTTP 客户端 per 线程。

您似乎正在阻塞等待回复的线程,并且您正在从一个不做任何额外操作的线程执行此操作。那为什么还要打扰 async/await 呢?您可以使用简单的阻塞调用。

另外 - 您的程序现在在启动线程后立即结束。在从 main 返回之前,您可能希望等待线程完成。您可以在程序结束时通过以下代码执行此操作:

Thread1.Join();
Thread2.Join();

基于 cmets 的更新:

    决定您要发出多少并行请求 - 这将是您的线程数。 使用 ManualResetEvent 使主线程等待信号。 在每个线程中不断提交您的请求。一旦您得到等待的答案 - 发出 ManualResetEvent 信号并允许您的主函数返回。

【讨论】:

嗯,我不希望阻止任何调用,而是同时运行相同的 POST 请求,直到收到 200 代码。如果程序在启动线程后关闭,可能会出现什么问题?我不需要程序在 getSession() 之后继续运行。 就目前而言,您的程序将在 Thread2.Start() 完成后立即完成。当你的程序完成运行时——操作系统会小心销毁所有启动的线程,你不会得到任何结果。

以上是关于我的多线程 HttpClient 有啥问题吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Qt 的多线程应用程序有啥问题(错误 SIGSEGV)

Delphi XE 中的多线程有啥新功能?

如果 GIL 存在,Python 中的多线程有啥意义?

这有啥意义吗,我的电脑只能并行运行 4 个线程?

HttpClient对高并发有啥优化吗

未调用线程函数。语法有啥问题吗