ilovecomputer66 发表于 2023-1-30 09:56

C# 自带 HttpClient我这段代码为什么会报请求没完成就发另一个呢?我觉得完成了啊

public class TestUseHttpClientClass
    {
      private HttpClient _HttpClient;

      public TestUseHttpClientClass()
      {
            HttpClientHandler handler = new HttpClientHandler();
            handler.AllowAutoRedirect = true;
            handler.CheckCertificateRevocationList = false;
            handler.UseCookies = false;

            _HttpClient = new HttpClient(handler);
      }

      public bool DoTask(int timeoutSecond)
      {
            _HttpClient.Timeout = TimeSpan.FromMilliseconds(timeoutSecond * 1000L);

            HttpRequestMessage req = new HttpRequestMessage();
            req.Method = HttpMethod.Get;
            req.RequestUri = new Uri("http://www.baidu.com");

            HttpResponseMessage response = null;
            try
            {
                var task = _HttpClient.SendAsync(req);
                task.Wait();
                response = task.Result;
                string responseJson = response.Content.ReadAsStringAsync().Result;
                if (responseJson.Contains("百度"))
                  return true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine("TestUseHttpClientClass DoTask 出错了!!!");
                Debug.WriteLine(ex);
            }
            return false;
      }
    }



然后在winform的一个button的点击事件中



private void button7_Click(object sender, EventArgs e)
      {
            TestUseHttpClientClass test = new TestUseHttpClientClass();
            if (test.DoTask(10) == true)
                Debug.WriteLine("第一次运行成功");
            else
                Debug.WriteLine("第一次运行失败");

            if (test.DoTask(20) == true)
                Debug.WriteLine("第二次运行成功");
            else
                Debug.WriteLine("第二次运行失败");
      }

结果运行报错:

System.InvalidOperationException
HResult=0x80131509
Message=This instance has already started one or more requests. Properties can only be modified before sending the first request.
Source=System.Net.Http
StackTrace:
   在 System.Net.Http.HttpClient.CheckDisposedOrStarted()
   在 System.Net.Http.HttpClient.set_Timeout(TimeSpan value)
   在 TestImageLib.TestUseHttpClientClass.DoTask(Int32 timeoutSecond) 在MainForm.cs 中: 第 290 行
   在 TestImageLib.MainForm.button7_Click(Object sender, EventArgs e) 在 MainForm.cs 中: 第 267 行
   在 System.Windows.Forms.Control.OnClick(EventArgs e)
   在 System.Windows.Forms.Button.OnClick(EventArgs e)
   在 System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   在 System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   在 System.Windows.Forms.Control.WndProc(Message& m)
   在 System.Windows.Forms.ButtonBase.WndProc(Message& m)
   在 System.Windows.Forms.Button.WndProc(Message& m)
   在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   在 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam)
   在 Interop.User32.DispatchMessageW(MSG& msg)
   在 System.Windows.Forms.Application.ComponentManager.Interop.Mso.IMsoComponentManager.FPushMessageLoop(UIntPtr dwComponentID, msoloop uReason, Void* pvLoopData)
   在 System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(msoloop reason, ApplicationContext context)
   在 System.Windows.Forms.Application.ThreadContext.RunMessageLoop(msoloop reason, ApplicationContext context)
   在 System.Windows.Forms.Application.Run(Form mainForm)
   在 TestImageLib.Program.Main() 在 Program.cs 中: 第 14 行


所以就想问下,在一个请求完成后,都已经拿完了返回结果了,这个还不算是已经关闭么?要具体如何处理呢?谢谢

ilovecomputer66 发表于 2023-1-30 10:35

本帖最后由 ilovecomputer66 于 2023-1-30 10:41 编辑

补充:和httpclient那块同步、异步,我测试了没关系

改为
HttpResponseMessage resp = _HttpClient.Send(req);
                string responseJson = resp.Content.ToString();

仍旧一样,在第二次设置timeout就会报错


并且,只要去掉设置timeout这句话,就不会出问题,无论后面同步请求还是异步


这样,问题变成,这个timeout为何不能第二次设置??

闲月疏云 发表于 2023-1-30 10:44

"Properties can only be modified before sending the first request."的翻译是“只能在**首次请求前**修改属性。” 这是个英语问题。

13570648032 发表于 2023-1-30 10:55

本帖最后由 13570648032 于 2023-1-30 10:59 编辑

你这个是因为你的按钮事件请求了2次URL,2次url的HTTPclient对象都是同一个,但timeout只能设置一次(这个应该是微软限制的),如果你要设置不同timeout就要new2个不同的HttpClinet对象。。。如果是同一个timeout,那可以调用同一个对象。(或者你看一下,同一个对象,请求不同的地址,是否可以设置不同的timeout,如果不同地址,不能设置不同的timeout,那证明,只能new不同的对象。或者,把new对象,封装成一个方法,把类里的httpClinet对象去掉。不能写成类成员,应该写成方法的变量传进去)。

whl2606555 发表于 2023-1-30 10:56

本帖最后由 whl2606555 于 2023-1-30 10:58 编辑

微软文档里面指出了这个问题,Timeout不能在首次请求发出后再设置,并且也给出了解决方案:
The same timeout will apply for all requests using this HttpClient instance. You may also set different timeouts for individual requests using a CancellationTokenSource on a task. Note that only the shorter of the two timeouts will apply.
对于不同的请求如果要复用一个HttpClient,可以用CancellationToken解决。但是CancellationToken的超时时间必须小于HttpClient设置的超时时间才有用,否则会直接触发HttpClient的超时。

ilovecomputer66 发表于 2023-1-30 10:58

闲月疏云 发表于 2023-1-30 10:44
"Properties can only be modified before sending the first request."的翻译是“只能在**首次请求前* ...

噢噢,我英文不好。这句确实理解错。但这个设置。我真的难以理解。为什么要强制固定这个?不知道底层怎么写的,才会有这种限制

whl2606555 发表于 2023-1-30 11:02

ilovecomputer66 发表于 2023-1-30 10:58
噢噢,我英文不好。这句确实理解错。但这个设置。我真的难以理解。为什么要强制固定这个?不知道底层怎么 ...

这个设定因为HttpClient的设计就是用来复用的,如果每个请求都new一个HttpClient可能会导致TCP连接过多(keepalive)。
而一旦复用HttpClient,如果允许二次修改HttpClient中和请求有关的属性可能会有并发问题,比如你在主线程中修改了超时时间,但是另一个线程中请求仍然在继续,这时候可能会有竟态条件问题或者是未定义行为。

bigcan 发表于 2023-1-30 11:02

本帖最后由 bigcan 于 2023-1-30 11:08 编辑

四楼是正解吧,HttpClient 用 using(HttpClient hc=new HttpClient()) 包起来,然后在内部设置timeout,用完即弃,设为类属性不太合理

using(HttpClient _HttpClient = new HttpClient())
            {
                _HttpClient.Timeout= TimeSpan.FromMilliseconds(1000 * 1000L);
                _HttpClient.Timeout = TimeSpan.FromMilliseconds(100 * 1000L);
                HttpRequestMessage req = new HttpRequestMessage();
                req.Method = HttpMethod.Get;
                req.RequestUri = new Uri("http://www.baidu.com");

                HttpResponseMessage response = null;
                try
                {
                  var task = _HttpClient.SendAsync(req);
                  task.Wait();
                  response = task.Result;
                  string responseJson = response.Content.ReadAsStringAsync().Result;
                  if (responseJson.Contains("百度"))
                        Console.WriteLine("百度");
                }
                catch (Exception ex)
                {
                  Console.WriteLine("TestUseHttpClientClass DoTask 出错了!!!");
                  Console.WriteLine(ex);
                }
            }

whl2606555 发表于 2023-1-30 11:04

guoyuhui 发表于 2023-1-30 10:59
1 给设置超时的这个注释了 应该可以解决
2 HttpClient 放在dotask里面每次 HttpClient client=new (); ...

不推荐每次请求都new一个HttpClient实例,微软解释的很清楚了:
HttpClient is intended to be instantiated once and reused throughout the life of an application. In .NET Core and .NET 5+, HttpClient pools connections inside the handler instance and reuses a connection across multiple requests. If you instantiate an HttpClient class for every request, the number of sockets available under heavy loads will be exhausted. This exhaustion will result in SocketException errors.
每次都new无法复用底层TCP连接,也没法使用连接池,还有可能会在大量请求中发生SocketException,导致sockets耗尽。

whl2606555 发表于 2023-1-30 11:05

bigcan 发表于 2023-1-30 11:02
三楼是正解吧,HttpClient 用 using(HttpClient hc=new HttpClient()) 包起来,然后在内部设置timeout,用 ...

HttpClient is intended to be instantiated once and reused throughout the life of an application. In .NET Core and .NET 5+, HttpClient pools connections inside the handler instance and reuses a connection across multiple requests. If you instantiate an HttpClient class for every request, the number of sockets available under heavy loads will be exhausted. This exhaustion will result in SocketException errors.
微软已经说明了不推荐HttpClient实例用完即弃,会导致很多问题。
页: [1] 2
查看完整版本: C# 自带 HttpClient我这段代码为什么会报请求没完成就发另一个呢?我觉得完成了啊